killbill-memoizeit

Changes

account/pom.xml 1(+0 -1)

beatrix/pom.xml 37(+35 -2)

invoice/pom.xml 3(+0 -3)

invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceItem.java 305(+0 -305)

pom.xml 11(+11 -0)

Details

account/pom.xml 1(+0 -1)

diff --git a/account/pom.xml b/account/pom.xml
index 141e6cb..8f2eeb1 100644
--- a/account/pom.xml
+++ b/account/pom.xml
@@ -94,7 +94,6 @@
         <dependency>
             <groupId>commons-io</groupId>
             <artifactId>commons-io</artifactId>
-            <version>2.0</version>
             <scope>test</scope>
         </dependency>
     </dependencies>
diff --git a/account/src/main/java/com/ning/billing/account/api/DefaultAccount.java b/account/src/main/java/com/ning/billing/account/api/DefaultAccount.java
index 8e5c0b3..2b916bf 100644
--- a/account/src/main/java/com/ning/billing/account/api/DefaultAccount.java
+++ b/account/src/main/java/com/ning/billing/account/api/DefaultAccount.java
@@ -19,16 +19,15 @@ package com.ning.billing.account.api;
 import java.util.List;
 import java.util.UUID;
 
-import com.google.inject.Inject;
-import com.ning.billing.util.clock.Clock;
 import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.util.customfield.CustomizableEntityBase;
 import com.ning.billing.util.tag.DefaultTagStore;
 import com.ning.billing.util.tag.DescriptiveTag;
 import com.ning.billing.util.tag.Tag;
 import com.ning.billing.util.tag.TagDefinition;
-import org.joda.time.DateTimeZone;
  
 public class DefaultAccount extends CustomizableEntityBase implements Account {
 	//public final static String OBJECT_TYPE = "Account";
@@ -68,13 +67,19 @@ public class DefaultAccount extends CustomizableEntityBase implements Account {
 				data.getPostalCode(), data.getPhone(), createdDate, createdDate);
 	}
 
-	/**
-	 * This call is used to migrate an account
-	 * @param data
-	 * @param createdDate
-	 */
-	public DefaultAccount(final AccountData data, DateTime createdDate,  DateTime updatedDate) {
-		this(UUID.randomUUID(), data.getExternalKey(), data.getEmail(), data.getName(), data.getFirstNameLength(),
+	//intended for creation
+	public DefaultAccount(final AccountData data) {
+		this(UUID.randomUUID(), data, null, null);
+	}
+	
+	// Intended for migration
+	public DefaultAccount(final AccountData data, DateTime createdDate, DateTime updatedDate) {
+		this(UUID.randomUUID(), data, createdDate, updatedDate);
+	}
+
+	//intended for update
+	public DefaultAccount(final UUID id, final AccountData data, DateTime createdDate, DateTime updatedDate) {
+		this(id, data.getExternalKey(), data.getEmail(), data.getName(), data.getFirstNameLength(),
 				data.getCurrency(), data.getBillCycleDay(), data.getPaymentProviderName(),
 				data.getTimeZone(), data.getLocale(),
 				data.getAddress1(), data.getAddress2(), data.getCompanyName(),
diff --git a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
index a6e2b7e..fd17c86 100644
--- a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
+++ b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
@@ -20,10 +20,12 @@ import java.util.List;
 import java.util.UUID;
 
 import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.account.api.AccountData;
 import com.ning.billing.account.api.DefaultAccount;
+import com.ning.billing.account.api.MigrationAccountData;
 import com.ning.billing.account.dao.AccountDao;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.customfield.CustomField;
@@ -74,8 +76,31 @@ public class DefaultAccountUserApi implements com.ning.billing.account.api.Accou
         dao.update(account);
     }
 
+    @Override
+    public void updateAccount(final String externalKey, final AccountData accountData) throws AccountApiException {
+    	UUID accountId = getIdFromKey(externalKey);
+    	if(accountId == null) {
+    		throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_KEY, externalKey);
+    	}
+    	Account account = new DefaultAccount(accountId, accountData);
+        dao.update(account);
+    }
+
 	@Override
 	public void deleteAccountByKey(String externalKey) throws AccountApiException {
 		dao.deleteByKey(externalKey);
 	}
+
+	@Override
+	public Account migrateAccount(MigrationAccountData data,
+			List<CustomField> fields, List<Tag> tags)
+			throws AccountApiException {
+		
+		Account account = new DefaultAccount(data);
+        account.addFields(fields);
+        account.addTags(tags);
+
+        dao.create(account);
+        return account;
+	}
 }
diff --git a/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java b/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java
index 28f0107..ac74b68 100644
--- a/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java
+++ b/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java
@@ -117,4 +117,19 @@ public class MockAccountUserApi implements AccountUserApi {
         }	
 		
 	}
+
+	@Override
+	public Account migrateAccount(MigrationAccountData data,
+			List<CustomField> fields, List<Tag> tags)
+			throws AccountApiException {
+		Account result = new DefaultAccount(data, data.getCreatedDate(), data.getUpdatedDate());
+        accounts.add(result);
+        return result;
+	}
+
+	@Override
+	public void updateAccount(String key, AccountData accountData)
+			throws AccountApiException {
+		throw new UnsupportedOperationException();
+	}
 }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java b/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java
index 59fc392..1b128b0 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java
@@ -21,8 +21,8 @@ import com.google.inject.AbstractModule;
 import com.ning.billing.analytics.AnalyticsListener;
 import com.ning.billing.analytics.BusinessAccountRecorder;
 import com.ning.billing.analytics.BusinessSubscriptionTransitionRecorder;
+import com.ning.billing.analytics.api.DefaultAnalyticsService;
 import com.ning.billing.analytics.api.AnalyticsService;
-import com.ning.billing.analytics.api.IAnalyticsService;
 import com.ning.billing.analytics.dao.BusinessAccountDao;
 import com.ning.billing.analytics.dao.BusinessAccountDaoProvider;
 import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionDao;
@@ -40,6 +40,6 @@ public class AnalyticsModule extends AbstractModule
         bind(BusinessAccountRecorder.class).asEagerSingleton();
         bind(AnalyticsListener.class).asEagerSingleton();
 
-        bind(IAnalyticsService.class).to(AnalyticsService.class).asEagerSingleton();
+        bind(AnalyticsService.class).to(DefaultAnalyticsService.class).asEagerSingleton();
     }
 }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java b/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
index 395d694..6054529 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
@@ -87,7 +87,7 @@ public class TestAnalyticsService
     private TagDefinitionSqlDao tagDao;
 
     @Inject
-    private AnalyticsService service;
+    private DefaultAnalyticsService service;
 
     @Inject
     private Bus bus;
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockIAccountUserApi.java b/analytics/src/test/java/com/ning/billing/analytics/MockIAccountUserApi.java
index 0722494..c6d2369 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockIAccountUserApi.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockIAccountUserApi.java
@@ -20,9 +20,11 @@ import java.util.List;
 import java.util.UUID;
 
 import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.account.api.AccountData;
 import com.ning.billing.account.api.AccountUserApi;
 import com.ning.billing.account.api.DefaultAccount;
+import com.ning.billing.account.api.MigrationAccountData;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.customfield.CustomField;
@@ -78,4 +80,17 @@ public class MockIAccountUserApi implements AccountUserApi
 	public void deleteAccountByKey(String externalKey) {
 		throw new UnsupportedOperationException();
 	}
+
+	@Override
+	public Account migrateAccount(MigrationAccountData data,
+			List<CustomField> fields, List<Tag> tags)
+			throws AccountApiException {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public void updateAccount(String key, AccountData accountData)
+			throws AccountApiException {
+		throw new UnsupportedOperationException();
+	}
 }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java b/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java
index 663ed78..10b5c70 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java
@@ -59,6 +59,11 @@ public class MockPhase implements PlanPhase
                 return BigDecimal.valueOf(price);
             }
 
+			@Override
+			public boolean isZero() {
+				return price == 0.0;
+			}
+
          };
     }
 
@@ -78,7 +83,11 @@ public class MockPhase implements PlanPhase
             {
                 return BigDecimal.valueOf(price);
             }
-
+            
+        	@Override
+			public boolean isZero() {
+				return price == 0.0;
+			}
         };
     }
 
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockPlan.java b/analytics/src/test/java/com/ning/billing/analytics/MockPlan.java
index 72ce7ca..d611eee 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockPlan.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockPlan.java
@@ -18,6 +18,7 @@ package com.ning.billing.analytics;
 
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.Product;
@@ -25,6 +26,8 @@ import com.ning.billing.catalog.api.Product;
 import java.util.Date;
 import java.util.Iterator;
 
+import org.joda.time.DateTime;
+
 public class MockPlan implements Plan
 {
     private final String name;
@@ -98,4 +101,10 @@ public class MockPlan implements Plan
 	public boolean isRetired() {
 		return false;
 	}
+
+	@Override
+	public DateTime dateOfFirstRecurringNonZeroCharge(
+			DateTime subscriptionStartDate) {
+		 throw new UnsupportedOperationException();
+	}
 }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java b/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
index 7197ec6..f592c41 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
@@ -133,7 +133,8 @@ public class MockSubscription implements Subscription
     public List<SubscriptionTransition> getAllTransitions() {
         throw new UnsupportedOperationException();
      }
-    
+
+    @Override
     public SubscriptionTransition getPendingTransition() {
         throw new UnsupportedOperationException();
     }
@@ -147,4 +148,9 @@ public class MockSubscription implements Subscription
 	public DateTime getPaidThroughDate() {
 		throw new UnsupportedOperationException();
 	}
+
+    @Override
+    public SubscriptionTransition getPreviousTransition() {
+        return null;
+    }
 }
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountApiException.java b/api/src/main/java/com/ning/billing/account/api/AccountApiException.java
index d9761b6..a6b1460 100644
--- a/api/src/main/java/com/ning/billing/account/api/AccountApiException.java
+++ b/api/src/main/java/com/ning/billing/account/api/AccountApiException.java
@@ -20,7 +20,9 @@ import com.ning.billing.BillingExceptionBase;
 import com.ning.billing.ErrorCode;
 
 public class AccountApiException extends BillingExceptionBase {
-    public AccountApiException(Throwable cause, int code, final String msg) {
+	private static final long serialVersionUID = 1L;
+
+	public AccountApiException(Throwable cause, int code, final String msg) {
         super(cause, code, msg);
     }
 
@@ -31,4 +33,5 @@ public class AccountApiException extends BillingExceptionBase {
     public AccountApiException(ErrorCode code, final Object... args) {
         super(code, args);
     }
+
 }
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountUserApi.java b/api/src/main/java/com/ning/billing/account/api/AccountUserApi.java
index e208c83..05d8660 100644
--- a/api/src/main/java/com/ning/billing/account/api/AccountUserApi.java
+++ b/api/src/main/java/com/ning/billing/account/api/AccountUserApi.java
@@ -25,6 +25,8 @@ public interface AccountUserApi {
 
     public Account createAccount(AccountData data, List<CustomField> fields, List<Tag> tags) throws AccountApiException;
 
+    public Account migrateAccount(MigrationAccountData data, List<CustomField> fields, List<Tag> tags) throws AccountApiException;
+
     /***
      *
      * Note: does not update the external key
@@ -32,6 +34,8 @@ public interface AccountUserApi {
      */
     public void updateAccount(Account account) throws AccountApiException;
 
+    public void updateAccount(String key, AccountData accountData) throws AccountApiException;
+
     public Account getAccountByKey(String key);
 
     public Account getAccountById(UUID accountId);
diff --git a/api/src/main/java/com/ning/billing/catalog/api/Catalog.java b/api/src/main/java/com/ning/billing/catalog/api/Catalog.java
index 3c04a74..2b3609a 100644
--- a/api/src/main/java/com/ning/billing/catalog/api/Catalog.java
+++ b/api/src/main/java/com/ning/billing/catalog/api/Catalog.java
@@ -24,11 +24,11 @@ public interface Catalog {
     //
     public abstract String getCatalogName();
 
-    public abstract Currency[] getSupportedCurrencies(DateTime requestedDate);
+    public abstract Currency[] getSupportedCurrencies(DateTime requestedDate) throws CatalogApiException;
 
-	public abstract Product[] getProducts(DateTime requestedDate);
+	public abstract Product[] getProducts(DateTime requestedDate) throws CatalogApiException;
 	
-	public abstract Plan[] getPlans(DateTime requestedDate);
+	public abstract Plan[] getPlans(DateTime requestedDate) throws CatalogApiException;
 
 	
 	//
diff --git a/api/src/main/java/com/ning/billing/catalog/api/InternationalPrice.java b/api/src/main/java/com/ning/billing/catalog/api/InternationalPrice.java
index 2876f1d..e047175 100644
--- a/api/src/main/java/com/ning/billing/catalog/api/InternationalPrice.java
+++ b/api/src/main/java/com/ning/billing/catalog/api/InternationalPrice.java
@@ -25,4 +25,6 @@ public interface InternationalPrice {
 
 	public abstract BigDecimal getPrice(Currency currency) throws CatalogApiException;
 
+	public abstract boolean isZero();
+
 }
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/catalog/api/Plan.java b/api/src/main/java/com/ning/billing/catalog/api/Plan.java
index c0dbce5..3abbfbe 100644
--- a/api/src/main/java/com/ning/billing/catalog/api/Plan.java
+++ b/api/src/main/java/com/ning/billing/catalog/api/Plan.java
@@ -19,6 +19,8 @@ package com.ning.billing.catalog.api;
 import java.util.Date;
 import java.util.Iterator;
 
+import org.joda.time.DateTime;
+
 public interface Plan {
 
 	public abstract PlanPhase[] getInitialPhases();
@@ -42,4 +44,7 @@ public interface Plan {
 	public abstract Date getEffectiveDateForExistingSubscriptons();
 
 	public abstract PlanPhase findPhase(String name) throws CatalogApiException;
+
+	public abstract DateTime dateOfFirstRecurringNonZeroCharge(DateTime subscriptionStartDate);
+	
 }
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/catalog/api/StaticCatalog.java b/api/src/main/java/com/ning/billing/catalog/api/StaticCatalog.java
index 0db7db5..c93fde8 100644
--- a/api/src/main/java/com/ning/billing/catalog/api/StaticCatalog.java
+++ b/api/src/main/java/com/ning/billing/catalog/api/StaticCatalog.java
@@ -25,13 +25,13 @@ public interface StaticCatalog {
     //
     public abstract String getCatalogName();
     
-    public abstract Date getEffectiveDate();
+    public abstract Date getEffectiveDate() throws CatalogApiException;
 
-    public abstract Currency[] getCurrentSupportedCurrencies();
+    public abstract Currency[] getCurrentSupportedCurrencies() throws CatalogApiException;
 
-	public abstract Product[] getCurrentProducts();
+	public abstract Product[] getCurrentProducts() throws CatalogApiException;
 	
-	public abstract Plan[] getCurrentPlans();
+	public abstract Plan[] getCurrentPlans() throws CatalogApiException;
 	
 	//
 	// Find a plan
diff --git a/api/src/main/java/com/ning/billing/config/EntitlementConfig.java b/api/src/main/java/com/ning/billing/config/EntitlementConfig.java
index 42399ce..1b6f3e2 100644
--- a/api/src/main/java/com/ning/billing/config/EntitlementConfig.java
+++ b/api/src/main/java/com/ning/billing/config/EntitlementConfig.java
@@ -33,7 +33,7 @@ public interface EntitlementConfig {
     @Default("500")
     public long getNotificationSleepTimeMs();
 
-    @Config("killbill.entitlement.engine.events.off")
+    @Config("killbill.notifications.off")
     @Default("false")
     public boolean isEventProcessingOff();
 }
diff --git a/api/src/main/java/com/ning/billing/config/InvoiceConfig.java b/api/src/main/java/com/ning/billing/config/InvoiceConfig.java
index a2b4270..78cc02f 100644
--- a/api/src/main/java/com/ning/billing/config/InvoiceConfig.java
+++ b/api/src/main/java/com/ning/billing/config/InvoiceConfig.java
@@ -33,7 +33,7 @@ public interface InvoiceConfig {
     @Default("500")
     public long getNotificationSleepTimeMs();
 
-    @Config("killbill.invoice.engine.events.off")
+    @Config("killbill.notifications.off")
     @Default("false")
     public boolean isEventProcessingOff();
 }
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/EntitlementService.java b/api/src/main/java/com/ning/billing/entitlement/api/EntitlementService.java
index 53854d1..28084b2 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/EntitlementService.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/EntitlementService.java
@@ -18,7 +18,6 @@ package com.ning.billing.entitlement.api;
 
 import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
 import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi;
-import com.ning.billing.entitlement.api.test.EntitlementTestApi;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.lifecycle.KillbillService;
 
@@ -31,7 +30,5 @@ public interface EntitlementService extends KillbillService {
 
     public EntitlementBillingApi getBillingApi();
 
-    public EntitlementTestApi getTestApi();
-
     public EntitlementMigrationApi getMigrationApi();
 }
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java b/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java
index 41ecd78..5c626fc 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java
@@ -64,7 +64,7 @@ public interface Subscription {
     public String getCurrentPriceList();
 
     public PlanPhase getCurrentPhase();
-    
+
     public DateTime getChargedThroughDate();
 
     public DateTime getPaidThroughDate();
@@ -76,4 +76,5 @@ public interface Subscription {
 
     public SubscriptionTransition getPendingTransition();
 
+    public SubscriptionTransition getPreviousTransition();
 }
diff --git a/api/src/main/java/com/ning/billing/ErrorCode.java b/api/src/main/java/com/ning/billing/ErrorCode.java
index 4e1ffaa..f7825e9 100644
--- a/api/src/main/java/com/ning/billing/ErrorCode.java
+++ b/api/src/main/java/com/ning/billing/ErrorCode.java
@@ -29,7 +29,8 @@ public enum ErrorCode {
      *
      */
     /* Generic through APIs */
-    ENT_INVALID_REQUESTED_DATE(1001, "Requested in the future is not allowed : %s"),
+    ENT_INVALID_REQUESTED_FUTURE_DATE(1001, "Requested date %s in the future is not allowed"),
+    ENT_INVALID_REQUESTED_DATE(1001, "Requested date %s is not allowed to be prior to the previous transition %s"),
 
     /* Creation */
     ENT_CREATE_BAD_PHASE(1011, "Can't create plan initial phase %s"),
@@ -93,12 +94,12 @@ public enum ErrorCode {
      */
     CAT_NO_CATALOG_FOR_GIVEN_DATE(2050, "There is no catalog version that applies for the given date '%s'"),
     CAT_NO_CATALOG_ENTRIES_FOR_GIVEN_DATE(2051, "The are no catalog entries that apply for the given date '%s'"),
-    CAT_CATALOG_NAME_MISMATCH(2052, "The catalog name '%s' does not match the name of the catalog we are trying to add '%s'"),  
+    CAT_CATALOG_NAME_MISMATCH(2052, "The catalog name '%s' does not match the name of the catalog we are trying to add '%s'"),
     /*
      * Billing Alignment
      */
     CAT_INVALID_BILLING_ALIGNMENT(2060, "Invalid billing alignment '%s'"),
-    
+
    /*
     *
     * Range 3000 : ACCOUNT
@@ -120,7 +121,7 @@ public enum ErrorCode {
     TAG_DEFINITION_ALREADY_EXISTS(3901, "The tag definition name already exists (name: %s)"),
     TAG_DEFINITION_DOES_NOT_EXIST(3902, "The tag definition name does not exist (name: %s)"),
     TAG_DEFINITION_IN_USE(3903, "The tag definition name is currently in use (name: %s)"),
-   
+
    /*
     *
     * Range 4000: INVOICE
@@ -128,7 +129,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/Invoice.java b/api/src/main/java/com/ning/billing/invoice/api/Invoice.java
index 0d13363..4a61849 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/Invoice.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/Invoice.java
@@ -31,6 +31,8 @@ public interface Invoice extends Entity {
 
     List<InvoiceItem> getInvoiceItems();
 
+    List<InvoiceItem> getInvoiceItems(Class clazz);
+
     int getNumberOfItems();
 
     boolean addPayment(InvoicePayment payment);
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..1abcf83 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,15 @@ public interface InvoiceItem extends Entity, Comparable<InvoiceItem> {
 
     String getPhaseName();
 
+    String getDescription();
+
     DateTime getStartDate();
 
     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/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java b/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
index 2983a22..df7d8ef 100644
--- a/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
@@ -47,7 +47,7 @@ public interface PaymentApi {
 
     Either<PaymentError, String> createPaymentProviderAccount(Account account);
 
-    Either<PaymentError, Void> updatePaymentProviderAccountContact(Account account);
+    Either<PaymentError, Void> updatePaymentProviderAccountContact(String accountKey);
 
     PaymentAttempt getPaymentAttemptForPaymentId(String id);
 
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaypalPaymentMethodInfo.java b/api/src/main/java/com/ning/billing/payment/api/PaypalPaymentMethodInfo.java
index 0977071..3b2ebed 100644
--- a/api/src/main/java/com/ning/billing/payment/api/PaypalPaymentMethodInfo.java
+++ b/api/src/main/java/com/ning/billing/payment/api/PaypalPaymentMethodInfo.java
@@ -16,6 +16,9 @@
 
 package com.ning.billing.payment.api;
 
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+
 import com.google.common.base.Strings;
 
 
@@ -54,11 +57,12 @@ public final class PaypalPaymentMethodInfo extends PaymentMethodInfo {
     private final String baid;
     private final String email;
 
-    public PaypalPaymentMethodInfo(String id,
-                                   String accountId,
-                                   Boolean defaultMethod,
-                                   String baid,
-                                   String email) {
+    @JsonCreator
+    public PaypalPaymentMethodInfo(@JsonProperty("id") String id,
+                                   @JsonProperty("accountId") String accountId,
+                                   @JsonProperty("defaultMethod") Boolean defaultMethod,
+                                   @JsonProperty("baid") String baid,
+                                   @JsonProperty("email") String email) {
         super(id, accountId, defaultMethod, TYPE);
 
         if (Strings.isNullOrEmpty(baid) || Strings.isNullOrEmpty(email)) {

beatrix/pom.xml 37(+35 -2)

diff --git a/beatrix/pom.xml b/beatrix/pom.xml
index ded6f0f..8fe8863 100644
--- a/beatrix/pom.xml
+++ b/beatrix/pom.xml
@@ -8,7 +8,8 @@
     OR CONDITIONS OF ANY KIND, either express or implied. See the ~ License for 
     the specific language governing permissions and limitations ~ under the License. -->
 
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <groupId>com.ning.billing</groupId>
@@ -34,6 +35,10 @@
         </dependency>
         <dependency>
             <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-payment</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
             <artifactId>killbill-catalog</artifactId>
         </dependency>
         <dependency>
@@ -47,7 +52,7 @@
         <dependency>
             <groupId>com.google.inject</groupId>
             <artifactId>guice</artifactId>
-            <version>3.0</version>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.skife.config</groupId>
@@ -58,6 +63,12 @@
             <artifactId>joda-time</artifactId>
         </dependency>
         <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-payment</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
             <groupId>com.ning.jdbi</groupId>
             <artifactId>jdbi-metrics</artifactId>
             <scope>test</scope>
@@ -88,11 +99,33 @@
             <type>test-jar</type>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>com.mysql</groupId>
+            <artifactId>management</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.mysql</groupId>
+            <artifactId>management-dbfiles</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <scope>runtime</scope>
+        </dependency>
     </dependencies>
     <build>
         <plugins>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <groups>fast,slow</groups>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-jar-plugin</artifactId>
                 <executions>
                     <execution>
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/lifecycle/TestLifecycle.java b/beatrix/src/test/java/com/ning/billing/beatrix/lifecycle/TestLifecycle.java
index 9bf7c10..f60d61f 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/lifecycle/TestLifecycle.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/lifecycle/TestLifecycle.java
@@ -121,7 +121,7 @@ public class TestLifecycle {
 
 
 
-    @BeforeClass
+    @BeforeClass(alwaysRun=true)
     public void setup() {
         final Injector g = Guice.createInjector(Stage.DEVELOPMENT, new TestLifecycleModule());
         s1 = g.getInstance(Service1.class);
diff --git a/beatrix/src/test/resources/catalogSample.xml b/beatrix/src/test/resources/catalogSample.xml
index 325f731..c18816f 100644
--- a/beatrix/src/test/resources/catalogSample.xml
+++ b/beatrix/src/test/resources/catalogSample.xml
@@ -200,16 +200,16 @@ Use Cases to do:
 		<plan name="pistol-monthly">
 			<product>Pistol</product>
 			<initialPhases>
-                                <phase type="TRIAL">
-                                        <duration>
-                                                <unit>DAYS</unit>
-                                                <number>30</number>
-                                        </duration>
-                                        <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
-                                        <fixedPrice>
-                                        </fixedPrice>
-                                    <!-- no price implies $0 -->
-                                </phase>
+                <phase type="TRIAL">
+                    <duration>
+                        <unit>DAYS</unit>
+                        <number>30</number>
+                    </duration>
+                    <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+                    <fixedPrice>
+                    </fixedPrice>
+                    <!-- no price implies $0 -->
+                </phase>
 			</initialPhases>
 			<finalPhase type="EVERGREEN">
 				<duration>
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java
index 878497b..0e821ef 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java
@@ -52,7 +52,7 @@ public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> impleme
 
     @Override
     public DateTime addToDateTime(DateTime dateTime) {
-        if (number < 0) {return null;}
+        if (number == null) {return dateTime;}
 
         switch (unit) {
             case DAYS:
@@ -63,9 +63,9 @@ public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> impleme
                 return dateTime.plusYears(number);
             case UNLIMITED:
                 return dateTime.plusYears(100);
+            default:
+                return dateTime;
         }
-
-        return null;
     }
 
     @Override
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultInternationalPrice.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultInternationalPrice.java
index 1c505f9..d0cf2c4 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultInternationalPrice.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultInternationalPrice.java
@@ -119,4 +119,18 @@ public class DefaultInternationalPrice extends ValidatingConfig<StandaloneCatalo
 		return zeroPrice;
 	}
 
+	@Override
+	public boolean isZero() {
+		for(DefaultPrice price :prices) {
+			try {
+				if( price.getValue().compareTo(BigDecimal.ZERO) != 0) {
+					return false;
+				}
+			} catch (CurrencyValueNull e) {
+				//Ignore if the currency is null we treat it as 0
+			}
+		}
+		return true;
+	}
+
 }
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultPlan.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultPlan.java
index 4972e26..a249289 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultPlan.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultPlan.java
@@ -29,9 +29,12 @@ import javax.xml.bind.annotation.XmlElementWrapper;
 import javax.xml.bind.annotation.XmlID;
 import javax.xml.bind.annotation.XmlIDREF;
 
+import org.joda.time.DateTime;
+
 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.PhaseType;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.Product;
@@ -228,6 +231,18 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements 
 		this.plansAllowedInBundle = plansAllowedInBundle;
 		return this;
 	}
+	@Override
+	public DateTime dateOfFirstRecurringNonZeroCharge(DateTime subscriptionStartDate) {
+		DateTime result = subscriptionStartDate.toDateTime();
+		for (PlanPhase phase : getAllPhases()) {
+			if(phase.getRecurringPrice() == null || phase.getRecurringPrice().isZero()) {
+				result = phase.getDuration().addToDateTime(result);
+			} else {
+				break;
+			}
+		}
+		return result;
+	}
 	
 	
 }
diff --git a/catalog/src/main/java/com/ning/billing/catalog/VersionedCatalog.java b/catalog/src/main/java/com/ning/billing/catalog/VersionedCatalog.java
index f652347..ef9b125 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/VersionedCatalog.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/VersionedCatalog.java
@@ -66,24 +66,16 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
 
 	public VersionedCatalog(Clock clock) {
 		this.clock = clock;
-		StandaloneCatalog baseline = new StandaloneCatalog(new Date(0)); // init with an empty catalog may need to 
-													 // populate some empty pieces here to make validation work
-		try {
-			add(baseline);
-		} catch (CatalogApiException e) {
-			// This should never happen
-			log.error("This error should never happpen", e);
-		} 
 	}
 
 	//
 	// Private methods
 	//
-	private StandaloneCatalog versionForDate(DateTime date) {
+	private StandaloneCatalog versionForDate(DateTime date) throws CatalogApiException {
 		return versions.get(indexOfVersionForDate(date.toDate()));
 	}
 
-	private List<StandaloneCatalog> versionsBeforeDate(Date date) {
+	private List<StandaloneCatalog> versionsBeforeDate(Date date) throws CatalogApiException {
 		List<StandaloneCatalog> result = new ArrayList<StandaloneCatalog>();
 		int index = indexOfVersionForDate(date);
 		for(int i = 0; i <= index; i++) {
@@ -92,14 +84,14 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
 		return result;
 	}
 
-	private int indexOfVersionForDate(Date date) {
-		for(int i = 1; i < versions.size(); i++) {
+	private int indexOfVersionForDate(Date date) throws CatalogApiException {
+		for(int i = versions.size() - 1; i >= 0; i--) {
 			StandaloneCatalog c = versions.get(i);
-			if(c.getEffectiveDate().getTime() > date.getTime()) {
-				return i - 1;
+			if(c.getEffectiveDate().getTime() < date.getTime()) {
+				return i;
 			}
 		}
-		return versions.size() - 1;
+		throw new CatalogApiException(ErrorCode.CAT_NO_CATALOG_FOR_GIVEN_DATE, date.toString());
 	}
 	
 	private class PlanRequestWrapper {
@@ -205,17 +197,17 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
 	}
 
 	@Override
-	public DefaultProduct[] getProducts(DateTime requestedDate) {
+	public DefaultProduct[] getProducts(DateTime requestedDate) throws CatalogApiException {
 		return versionForDate(requestedDate).getCurrentProducts();
 	}
 
 	@Override
-	public Currency[] getSupportedCurrencies(DateTime requestedDate) {
+	public Currency[] getSupportedCurrencies(DateTime requestedDate) throws CatalogApiException {
 		return versionForDate(requestedDate).getCurrentSupportedCurrencies();
 	}
 
 	@Override
-	public DefaultPlan[] getPlans(DateTime requestedDate) {
+	public DefaultPlan[] getPlans(DateTime requestedDate) throws CatalogApiException {
 		return versionForDate(requestedDate).getCurrentPlans();
 	}
 
@@ -350,22 +342,22 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
 	// Static catalog API
 	//
 	@Override
-	public Date getEffectiveDate() {
+	public Date getEffectiveDate() throws CatalogApiException {
 		return versionForDate(clock.getUTCNow()).getEffectiveDate();
 	}
 
 	@Override
-	public Currency[] getCurrentSupportedCurrencies() {
+	public Currency[] getCurrentSupportedCurrencies() throws CatalogApiException {
 		return versionForDate(clock.getUTCNow()).getCurrentSupportedCurrencies();
 	}
 
 	@Override
-	public Product[] getCurrentProducts() {
+	public Product[] getCurrentProducts() throws CatalogApiException {
 		return versionForDate(clock.getUTCNow()).getCurrentProducts() ;
 	}
 
 	@Override
-	public Plan[] getCurrentPlans() {
+	public Plan[] getCurrentPlans() throws CatalogApiException {
 		return versionForDate(clock.getUTCNow()).getCurrentPlans();
 	}
 
diff --git a/catalog/src/test/java/com/ning/billing/catalog/io/TestVersionedCatalogLoader.java b/catalog/src/test/java/com/ning/billing/catalog/io/TestVersionedCatalogLoader.java
index 9708b99..0d5ac66 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/io/TestVersionedCatalogLoader.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/io/TestVersionedCatalogLoader.java
@@ -121,9 +121,8 @@ public class TestVersionedCatalogLoader {
 	@Test(enabled=true)
 	public void testLoad() throws MalformedURLException, IOException, SAXException, InvalidConfigException, JAXBException, TransformerException, URISyntaxException, ServiceException {
 		VersionedCatalog c = loader.load(Resources.getResource("versionedCatalog").toString());
-		assertEquals(4, c.size());
+		assertEquals(3, c.size());
 		Iterator<StandaloneCatalog> it = c.iterator();
-		it.next(); //discard the baseline
 		DateTime dt = new DateTime("2011-01-01T00:00:00+00:00");
 		assertEquals(dt.toDate(),it.next().getEffectiveDate());
 		dt = new DateTime("2011-02-02T00:00:00+00:00");
diff --git a/catalog/src/test/java/com/ning/billing/catalog/TestPlan.java b/catalog/src/test/java/com/ning/billing/catalog/TestPlan.java
index 00dd1b6..e58f71c 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/TestPlan.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/TestPlan.java
@@ -18,17 +18,21 @@ package com.ning.billing.catalog;
 
 import java.util.Date;
 
+import org.joda.time.DateTime;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
 import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.InternationalPrice;
+import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.util.config.ValidationErrors;
 
 public class TestPlan {
 	private static final Logger log = LoggerFactory.getLogger(TestPlan.class);
-	@Test
+	@Test(groups={"fast"}, enabled = true)
 	public void testDateValidation() {
 
 		StandaloneCatalog c = new MockCatalog();
@@ -40,4 +44,80 @@ public class TestPlan {
 		errors.log(log);
 
 	}
+	
+	private static class MyDuration extends DefaultDuration {
+		final int days;
+		
+		public MyDuration(int days) {
+			this.days = days;
+		}
+		
+		@Override
+		public DateTime addToDateTime(DateTime dateTime) {
+			return dateTime.plusDays(days);
+		}
+	}
+	
+	private static class MyPlanPhase extends MockPlanPhase {
+		Duration duration;
+		boolean recurringPriceIsZero;
+		
+		MyPlanPhase(int duration, boolean recurringPriceIsZero) {
+			this.duration= new MyDuration( duration );
+			this.recurringPriceIsZero = recurringPriceIsZero;
+		}
+		@Override
+		public Duration getDuration(){
+			return duration;
+		}
+		
+		@Override
+		public InternationalPrice getRecurringPrice() {
+			return new MockInternationalPrice() {
+				@Override
+				public boolean isZero() {
+					return recurringPriceIsZero;
+				}
+			};
+		}
+	}
+	
+	@Test(groups={"fast"}, enabled = true)
+	public void testDataCalc() {
+		DefaultPlan p0 =  new MockPlan() {
+			public PlanPhase[] getAllPhases() {
+				return new PlanPhase[]{
+						new MyPlanPhase(10, true),
+						new MyPlanPhase(10, false),
+				};
+			}
+		};
+		
+		DefaultPlan p1 =  new MockPlan() {
+			public PlanPhase[] getAllPhases() {
+				return new PlanPhase[]{
+						new MyPlanPhase(10, true),
+						new MyPlanPhase(10, true),
+						new MyPlanPhase(10, true),
+						new MyPlanPhase(10, true),
+						new MyPlanPhase(10, false),
+						new MyPlanPhase(10, true),
+				};
+			}
+		};
+		
+		DefaultPlan p2 =  new MockPlan() {
+			public PlanPhase[] getAllPhases() {
+				return new PlanPhase[]{
+						new MyPlanPhase(10, false),
+						new MyPlanPhase(10, true),
+				};
+			}
+		};
+		DateTime requestedDate = new DateTime();
+		Assert.assertEquals(p0.dateOfFirstRecurringNonZeroCharge(requestedDate), requestedDate.plusDays(10));
+		Assert.assertEquals(p1.dateOfFirstRecurringNonZeroCharge(requestedDate), requestedDate.plusDays(40));
+		Assert.assertEquals(p2.dateOfFirstRecurringNonZeroCharge(requestedDate), requestedDate.plusDays(0));
+
+	}
 }
diff --git a/catalog/src/test/java/com/ning/billing/catalog/TestVersionedCatalog.java b/catalog/src/test/java/com/ning/billing/catalog/TestVersionedCatalog.java
index ddd623e..fbc99df 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/TestVersionedCatalog.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/TestVersionedCatalog.java
@@ -35,6 +35,7 @@ import org.testng.annotations.Test;
 import org.xml.sax.SAXException;
 
 import com.google.common.io.Resources;
+import com.ning.billing.ErrorCode;
 import com.ning.billing.catalog.api.CatalogApiException;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.InvalidConfigException;
@@ -53,14 +54,14 @@ public class TestVersionedCatalog {
 		vc = loader.load(Resources.getResource("versionedCatalog").toString());
 	}
 
-	@Test(enabled=true)
+	@Test(groups={"fast"},enabled=true)
 	public void testAddCatalog() throws MalformedURLException, IOException, SAXException, InvalidConfigException, JAXBException, TransformerException, URISyntaxException, ServiceException, CatalogApiException {
 		vc.add(new StandaloneCatalog(new Date()));
-		assertEquals(5, vc.size());
+		assertEquals(4, vc.size());
 	}
 	
 		
-	@Test(enabled=true)
+	@Test(groups={"fast"},enabled=true)
 	public void testFindPlanWithDates() throws Exception {
 		DateTime dt0= new DateTime("2010-01-01T00:00:00+00:00");
 		DateTime dt1 = new DateTime("2011-01-01T00:01:00+00:00");
@@ -97,6 +98,18 @@ public class TestVersionedCatalog {
 		Assert.assertEquals(exSubPlan214.getAllPhases()[1].getRecurringPrice().getPrice(Currency.USD), new BigDecimal("2.0"));
 		Assert.assertEquals(exSubPlan3.getAllPhases()[1].getRecurringPrice().getPrice(Currency.USD), new BigDecimal("2.0"));
 
-		
+	}
+	
+	@Test(groups={"fast"},enabled=true)
+	public void testErrorOnDateTooEarly() {
+		DateTime dt0= new DateTime("1977-01-01T00:00:00+00:00");
+		try {
+			vc.findPlan("foo", dt0);
+			Assert.fail("Date is too early an exception should have been thrown");
+		} catch (CatalogApiException e) {
+			e.printStackTrace();
+			Assert.assertEquals(e.getCode(), ErrorCode.CAT_NO_CATALOG_FOR_GIVEN_DATE.getCode());
+
+		}
 	}
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultEntitlementBillingApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultEntitlementBillingApi.java
index a5199a9..ee9ec81 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultEntitlementBillingApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultEntitlementBillingApi.java
@@ -158,8 +158,11 @@ public class DefaultEntitlementBillingApi implements EntitlementBillingApi {
         } else {
             Date paidThroughDate = (subscription.getPaidThroughDate() == null) ? null : subscription.getPaidThroughDate().toDate();
 
-            subscriptionSqlDao.updateSubscription(subscriptionId.toString(), subscription.getActiveVersion(),
-                                                  ctd.toDate(), paidThroughDate);
+            DateTime chargedThroughDate = subscription.getChargedThroughDate();
+            if (chargedThroughDate == null || chargedThroughDate.isBefore(ctd)) {
+                subscriptionSqlDao.updateSubscription(subscriptionId.toString(), subscription.getActiveVersion(),
+                                                      ctd.toDate(), paidThroughDate);
+            }
         }
     }
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java
index d2af1a6..3143b7c 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java
@@ -99,10 +99,7 @@ public class SubscriptionApiService {
 
             DateTime now = clock.getUTCNow();
             requestedDate = (requestedDate != null) ? DefaultClock.truncateMs(requestedDate) : now;
-            // STEPH needs to check if requestedDate is before last 'erasable event'?
-            if (requestedDate != null && requestedDate.isAfter(now)) {
-                throw new EntitlementUserApiException(ErrorCode.ENT_INVALID_REQUESTED_DATE, requestedDate.toString());
-            }
+            validateRequestedDateOnChangeOrCancel(subscription, now, requestedDate);
 
             Plan currentPlan = subscription.getCurrentPlan();
             PlanPhaseSpecifier planPhase = new PlanPhaseSpecifier(currentPlan.getProduct().getName(),
@@ -160,6 +157,7 @@ public class SubscriptionApiService {
         subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId()), catalogService.getFullCatalog());
     }
 
+
     public void changePlan(SubscriptionData subscription, String productName, BillingPeriod term,
             String priceList, DateTime requestedDate)
         throws EntitlementUserApiException {
@@ -169,10 +167,7 @@ public class SubscriptionApiService {
 
             DateTime now = clock.getUTCNow();
             requestedDate = (requestedDate != null) ? DefaultClock.truncateMs(requestedDate) : now;
-            // STEPH needs to check if requestedDate is before last 'erasable event'?
-            if (requestedDate != null && requestedDate.isAfter(now)) {
-                throw new EntitlementUserApiException(ErrorCode.ENT_INVALID_REQUESTED_DATE, requestedDate.toString());
-            }
+            validateRequestedDateOnChangeOrCancel(subscription, now, requestedDate);
 
             String currentPriceList = subscription.getCurrentPriceList();
 
@@ -207,7 +202,7 @@ public class SubscriptionApiService {
             PriceList newPriceList = planChangeResult.getNewPriceList();
 
             Plan newPlan = catalogService.getFullCatalog().findPlan(productName, term, newPriceList.getName(), requestedDate);
-            DateTime effectiveDate = subscription.getPlanChangeEffectiveDate(policy, now);
+            DateTime effectiveDate = subscription.getPlanChangeEffectiveDate(policy, requestedDate);
 
             TimedPhase currentTimedPhase = planAligner.getCurrentTimedPhaseOnChange(subscription, newPlan, newPriceList.getName(), requestedDate, effectiveDate);
 
@@ -237,4 +232,18 @@ public class SubscriptionApiService {
             throw new EntitlementUserApiException(e);
         }
     }
+
+    private void validateRequestedDateOnChangeOrCancel(SubscriptionData subscription, DateTime now, DateTime requestedDate)
+        throws EntitlementUserApiException {
+
+        if (requestedDate.isAfter(now) ) {
+            throw new EntitlementUserApiException(ErrorCode.ENT_INVALID_REQUESTED_FUTURE_DATE, requestedDate.toString());
+        }
+
+        SubscriptionTransition previousTransition = subscription.getPreviousTransition();
+        if (previousTransition.getEffectiveTransitionTime().isAfter(requestedDate)) {
+            throw new EntitlementUserApiException(ErrorCode.ENT_INVALID_REQUESTED_DATE,
+                    requestedDate.toString(), previousTransition.getEffectiveTransitionTime());
+        }
+    }
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
index 342c9cc..f4bb22b 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
@@ -104,29 +104,29 @@ public class SubscriptionData implements Subscription {
 
     @Override
     public SubscriptionState getState() {
-        return (getLatestTransition() == null) ? null : getLatestTransition().getNextState();
+        return (getPreviousTransition() == null) ? null : getPreviousTransition().getNextState();
     }
 
     @Override
     public PlanPhase getCurrentPhase() {
-        return (getLatestTransition() == null) ? null : getLatestTransition().getNextPhase();
+        return (getPreviousTransition() == null) ? null : getPreviousTransition().getNextPhase();
     }
 
 
     @Override
     public Plan getCurrentPlan() {
-        return (getLatestTransition() == null) ? null : getLatestTransition().getNextPlan();
+        return (getPreviousTransition() == null) ? null : getPreviousTransition().getNextPlan();
     }
 
     @Override
     public String getCurrentPriceList() {
-        return (getLatestTransition() == null) ? null : getLatestTransition().getNextPriceList();
+        return (getPreviousTransition() == null) ? null : getPreviousTransition().getNextPriceList();
     }
 
 
     @Override
     public DateTime getEndDate() {
-        SubscriptionTransition latestTransition = getLatestTransition();
+        SubscriptionTransition latestTransition = getPreviousTransition();
         if (latestTransition.getNextState() == SubscriptionState.CANCELLED) {
             return latestTransition.getEffectiveTransitionTime();
         }
@@ -188,6 +188,7 @@ public class SubscriptionData implements Subscription {
         return result;
     }
 
+    @Override
     public SubscriptionTransition getPendingTransition() {
         if (transitions == null) {
             return null;
@@ -200,7 +201,7 @@ public class SubscriptionData implements Subscription {
         return null;
     }
 
-    public SubscriptionTransition getLatestTransition() {
+    public SubscriptionTransition getPreviousTransition() {
 
         if (transitions == null) {
             return null;
@@ -240,10 +241,12 @@ public class SubscriptionData implements Subscription {
         return bundleStartDate;
     }
 
+    @Override
     public DateTime getChargedThroughDate() {
         return chargedThroughDate;
     }
 
+    @Override
     public DateTime getPaidThroughDate() {
         return paidThroughDate;
     }
@@ -354,7 +357,7 @@ public class SubscriptionData implements Subscription {
         transitions = new LinkedList<SubscriptionTransitionData>();
         Plan previousPlan = null;
         PlanPhase previousPhase = null;
-        
+
         for (final EntitlementEvent cur : events) {
 
             if (!cur.isActive() || cur.getActiveVersion() < activeVersion) {
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
index ce70ba7..0f5375b 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
@@ -31,8 +31,6 @@ import com.ning.billing.entitlement.api.billing.DefaultEntitlementBillingApi;
 import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
 import com.ning.billing.entitlement.api.migration.DefaultEntitlementMigrationApi;
 import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi;
-import com.ning.billing.entitlement.api.test.DefaultEntitlementTestApi;
-import com.ning.billing.entitlement.api.test.EntitlementTestApi;
 import com.ning.billing.entitlement.api.user.DefaultEntitlementUserApi;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
@@ -65,7 +63,6 @@ public class Engine implements EventListener, EntitlementService {
     private final PlanAligner planAligner;
     private final EntitlementUserApi userApi;
     private final EntitlementBillingApi billingApi;
-    private final EntitlementTestApi testApi;
     private final EntitlementMigrationApi migrationApi;
     private final Bus eventBus;
     private final EntitlementConfig config;
@@ -76,7 +73,7 @@ public class Engine implements EventListener, EntitlementService {
     @Inject
     public Engine(Clock clock, EntitlementDao dao, PlanAligner planAligner,
             EntitlementConfig config, DefaultEntitlementUserApi userApi,
-            DefaultEntitlementBillingApi billingApi, DefaultEntitlementTestApi testApi,
+            DefaultEntitlementBillingApi billingApi,
             DefaultEntitlementMigrationApi migrationApi, Bus eventBus,
             NotificationQueueService notificationQueueService) {
         super();
@@ -84,7 +81,6 @@ public class Engine implements EventListener, EntitlementService {
         this.dao = dao;
         this.planAligner = planAligner;
         this.userApi = userApi;
-        this.testApi = testApi;
         this.billingApi = billingApi;
         this.migrationApi = migrationApi;
         this.config = config;
@@ -105,7 +101,7 @@ public class Engine implements EventListener, EntitlementService {
                     NOTIFICATION_QUEUE_NAME,
                     new NotificationQueueHandler() {
                 @Override
-                public void handleReadyNotification(String notificationKey) {
+                public void handleReadyNotification(String notificationKey, DateTime eventDateTime) {
                     EntitlementEvent event = dao.getEventById(UUID.fromString(notificationKey));
                     if (event == null) {
                         log.warn("Failed to extract event for notification key {}", notificationKey);
@@ -161,11 +157,6 @@ public class Engine implements EventListener, EntitlementService {
 
 
     @Override
-    public EntitlementTestApi getTestApi() {
-        return testApi;
-    }
-
-    @Override
     public EntitlementMigrationApi getMigrationApi() {
         return migrationApi;
     }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/glue/EntitlementModule.java b/entitlement/src/main/java/com/ning/billing/entitlement/glue/EntitlementModule.java
index c27a8f4..4f0dfec 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/glue/EntitlementModule.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/glue/EntitlementModule.java
@@ -27,8 +27,6 @@ import com.ning.billing.entitlement.api.billing.DefaultEntitlementBillingApi;
 import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
 import com.ning.billing.entitlement.api.migration.DefaultEntitlementMigrationApi;
 import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi;
-import com.ning.billing.entitlement.api.test.DefaultEntitlementTestApi;
-import com.ning.billing.entitlement.api.test.EntitlementTestApi;
 import com.ning.billing.entitlement.api.user.DefaultEntitlementUserApi;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.entitlement.api.user.SubscriptionApiService;
@@ -57,7 +55,6 @@ public class EntitlementModule extends AbstractModule {
         bind(Engine.class).asEagerSingleton();
         bind(PlanAligner.class).asEagerSingleton();
         bind(MigrationPlanAligner.class).asEagerSingleton();
-        bind(EntitlementTestApi.class).to(DefaultEntitlementTestApi.class).asEagerSingleton();
         bind(EntitlementUserApi.class).to(DefaultEntitlementUserApi.class).asEagerSingleton();
         bind(EntitlementBillingApi.class).to(DefaultEntitlementBillingApi.class).asEagerSingleton();
         bind(EntitlementMigrationApi.class).to(DefaultEntitlementMigrationApi.class).asEagerSingleton();
diff --git a/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql b/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql
index 55ad7f4..dfdc746 100644
--- a/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql
@@ -1,5 +1,5 @@
-DROP TABLE IF EXISTS events;
-CREATE TABLE events (
+DROP TABLE IF EXISTS entitlement_events;
+CREATE TABLE entitlement_events (
     id int(11) unsigned NOT NULL AUTO_INCREMENT,
     event_id char(36) NOT NULL,
     event_type varchar(9) NOT NULL,
diff --git a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg
index 704e2c7..10f565d 100644
--- a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg
@@ -15,14 +15,14 @@ getEventById(event_id) ::= <<
       , plist_name
       , current_version
       , is_active  
-  from events
+  from entitlement_events
   where
       event_id = :event_id
   ;
 >>
 
 insertEvent() ::= <<
-    insert into events (
+    insert into entitlement_events (
       event_id
       , event_type
       , user_type
@@ -54,14 +54,14 @@ insertEvent() ::= <<
 >>
 
 removeEvents(subscription_id) ::= <<
-    delete from events
+    delete from entitlement_events
       where
     subscription_id = :subscription_id
     ;
 >>
 
 unactiveEvent(event_id, now) ::= <<
-    update events
+    update entitlement_events
     set
       is_active = 0
       , updated_dt = :now
@@ -71,7 +71,7 @@ unactiveEvent(event_id, now) ::= <<
 >>
 
 reactiveEvent(event_id, now) ::= <<
-    update events
+    update entitlement_events
     set
       is_active = 1
       , updated_dt = :now
@@ -95,7 +95,7 @@ getFutureActiveEventForSubscription(subscription_id, now) ::= <<
       , plist_name
       , current_version
       , is_active
-    from events
+    from entitlement_events
     where
       subscription_id = :subscription_id
       and is_active = 1
@@ -123,7 +123,7 @@ getEventsForSubscription(subscription_id) ::= <<
       , plist_name
       , current_version
       , is_active
-    from events
+    from entitlement_events
     where
       subscription_id = :subscription_id
     order by
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccountUserApi.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccountUserApi.java
index 967d8b6..4602fe6 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccountUserApi.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccountUserApi.java
@@ -23,6 +23,7 @@ import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.account.api.AccountData;
 import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.account.api.MigrationAccountData;
 import com.ning.billing.util.customfield.CustomField;
 import com.ning.billing.util.tag.Tag;
 
@@ -66,4 +67,17 @@ public class BrainDeadAccountUserApi implements AccountUserApi {
 		throw new UnsupportedOperationException();
 	}
 
+	@Override
+	public Account migrateAccount(MigrationAccountData data,
+			List<CustomField> fields, List<Tag> tags)
+			throws AccountApiException {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public void updateAccount(String key, AccountData accountData)
+			throws AccountApiException {
+		throw new UnsupportedOperationException();
+	}
+
 }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadSubscription.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadSubscription.java
index e55ba79..98cc376 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadSubscription.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadSubscription.java
@@ -47,98 +47,103 @@ public class BrainDeadSubscription implements Subscription {
 			throws EntitlementUserApiException {
 		throw new UnsupportedOperationException();
 
-		
+
 	}
 
 	@Override
 	public void pause() throws EntitlementUserApiException {
 		throw new UnsupportedOperationException();
 
-		
+
 	}
 
 	@Override
 	public void resume() throws EntitlementUserApiException {
 		throw new UnsupportedOperationException();
-		
+
 	}
 
 	@Override
 	public UUID getId() {
 		throw new UnsupportedOperationException();
-		
+
 	}
 
 	@Override
 	public UUID getBundleId() {
 		throw new UnsupportedOperationException();
-		
+
 	}
 
 	@Override
 	public SubscriptionState getState() {
 		throw new UnsupportedOperationException();
-		
+
 	}
 
 	@Override
 	public DateTime getStartDate() {
 		throw new UnsupportedOperationException();
-		
+
 	}
 
 	@Override
 	public DateTime getEndDate() {
 		throw new UnsupportedOperationException();
-		
+
 	}
 
 	@Override
 	public Plan getCurrentPlan() {
 		throw new UnsupportedOperationException();
-		
+
 	}
 
 	@Override
 	public String getCurrentPriceList() {
 		throw new UnsupportedOperationException();
-		
+
 	}
 
 	@Override
 	public PlanPhase getCurrentPhase() {
 		throw new UnsupportedOperationException();
-		
+
 	}
 
 	@Override
 	public DateTime getChargedThroughDate() {
 		throw new UnsupportedOperationException();
-		
+
 	}
 
 	@Override
 	public DateTime getPaidThroughDate() {
 		throw new UnsupportedOperationException();
-		
+
 	}
 
 	@Override
 	public List<SubscriptionTransition> getActiveTransitions() {
 		throw new UnsupportedOperationException();
-		
+
 	}
 
 	@Override
 	public List<SubscriptionTransition> getAllTransitions() {
 		throw new UnsupportedOperationException();
-		
+
 	}
 
 	@Override
 	public SubscriptionTransition getPendingTransition() {
 		throw new UnsupportedOperationException();
-		
+
 	}
 
+    @Override
+    public SubscriptionTransition getPreviousTransition() {
+        return null;
+    }
+
 }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java
index 2204274..c5881f9 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java
@@ -58,7 +58,7 @@ public class MockEntitlementDaoSql extends EntitlementSqlDao implements MockEnti
 
     public static interface ResetSqlDao extends Transactional<ResetSqlDao>, CloseMe {
 
-        @SqlUpdate("truncate table events")
+        @SqlUpdate("truncate table entitlement_events")
         public void resetEvents();
 
         @SqlUpdate("truncate table subscriptions")

invoice/pom.xml 3(+0 -3)

diff --git a/invoice/pom.xml b/invoice/pom.xml
index e685de0..cc98907 100644
--- a/invoice/pom.xml
+++ b/invoice/pom.xml
@@ -71,18 +71,15 @@
         <dependency>
             <groupId>commons-io</groupId>
             <artifactId>commons-io</artifactId>
-            <version>2.0</version>
         </dependency>
         <dependency>
             <groupId>commons-io</groupId>
             <artifactId>commons-io</artifactId>
-            <version>2.0</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.jdbi</groupId>
             <artifactId>jdbi</artifactId>
-            <version>2.27</version>
         </dependency>
         <dependency>
             <groupId>org.antlr</groupId>
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 22849d1..3b27fa6 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
@@ -17,46 +17,63 @@
 package com.ning.billing.invoice.dao;
 
 import java.math.BigDecimal;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 
-import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
 import org.joda.time.DateTime;
 import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.TransactionStatus;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import com.google.inject.Inject;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
+import com.ning.billing.invoice.api.DefaultInvoiceService;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceCreationNotification;
 import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.invoice.api.InvoicePayment;
 import com.ning.billing.invoice.api.user.DefaultInvoiceCreationNotification;
-import com.ning.billing.invoice.notification.NextBillingDateNotifier;
+import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.invoice.notification.DefaultNextBillingDateNotifier;
+import com.ning.billing.invoice.notification.NextBillingDatePoster;
 import com.ning.billing.util.bus.Bus;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import com.ning.billing.util.notificationq.NotificationKey;
+import com.ning.billing.util.notificationq.NotificationQueue;
+import com.ning.billing.util.notificationq.NotificationQueueService;
+import com.ning.billing.util.notificationq.NotificationQueueService.NoSuchNotificationQueue;
 
 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 FixedPriceInvoiceItemSqlDao fixedPriceInvoiceItemSqlDao;
     private final InvoicePaymentSqlDao invoicePaymentSqlDao;
-    private final NextBillingDateNotifier notifier;
     private final EntitlementBillingApi entitlementBillingApi;
 
     private final Bus eventBus;
 
+	private NextBillingDatePoster nextBillingDatePoster;
+
     @Inject
     public DefaultInvoiceDao(final IDBI dbi, final Bus eventBus,
-                             final NextBillingDateNotifier notifier, final EntitlementBillingApi entitlementBillingApi) {
+                             final EntitlementBillingApi entitlementBillingApi,
+                             NextBillingDatePoster nextBillingDatePoster) {
         this.invoiceSqlDao = dbi.onDemand(InvoiceSqlDao.class);
-        this.invoiceItemSqlDao = dbi.onDemand(InvoiceItemSqlDao.class);
+        this.recurringInvoiceItemSqlDao = dbi.onDemand(RecurringInvoiceItemSqlDao.class);
+        this.fixedPriceInvoiceItemSqlDao = dbi.onDemand(FixedPriceInvoiceItemSqlDao.class);
         this.invoicePaymentSqlDao = dbi.onDemand(InvoicePaymentSqlDao.class);
         this.eventBus = eventBus;
-        this.notifier = notifier;
         this.entitlementBillingApi = entitlementBillingApi;
+        this.nextBillingDatePoster = nextBillingDatePoster;
     }
 
     @Override
@@ -91,7 +108,10 @@ public class DefaultInvoiceDao implements InvoiceDao {
 
     @Override
     public List<InvoiceItem> getInvoiceItemsByAccount(final UUID accountId) {
-        return invoiceItemSqlDao.getInvoiceItemsByAccount(accountId.toString());
+        List<InvoiceItem> results = new ArrayList<InvoiceItem>();
+        results.addAll(recurringInvoiceItemSqlDao.getInvoiceItemsByAccount(accountId.toString()));
+        results.addAll(fixedPriceInvoiceItemSqlDao.getInvoiceItemsByAccount(accountId.toString()));
+        return results;
     }
 
     @Override
@@ -117,13 +137,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());
-                     invoice.addInvoiceItems(invoiceItems);
-
-                     InvoicePaymentSqlDao invoicePaymentSqlDao = invoiceDao.become(InvoicePaymentSqlDao.class);
-                     List<InvoicePayment> invoicePayments = invoicePaymentSqlDao.getPaymentsForInvoice(invoiceId.toString());
-                     invoice.addPayments(invoicePayments);
+                     getInvoiceItemsWithinTransaction(invoice, invoiceDao);
+                     getInvoicePaymentsWithinTransaction(invoice, invoiceDao);
                  }
 
                  return invoice;
@@ -143,13 +158,17 @@ public class DefaultInvoiceDao implements InvoiceDao {
                 if (currentInvoice == null) {
                     invoiceDao.create(invoice);
 
-                    List<InvoiceItem> invoiceItems = invoice.getInvoiceItems();
-                    InvoiceItemSqlDao invoiceItemDao = invoiceDao.become(InvoiceItemSqlDao.class);
-                    invoiceItemDao.batchCreateFromTransaction(invoiceItems);
+                    List<InvoiceItem> recurringInvoiceItems = invoice.getInvoiceItems(RecurringInvoiceItem.class);
+                    RecurringInvoiceItemSqlDao recurringInvoiceItemDao = invoiceDao.become(RecurringInvoiceItemSqlDao.class);
+                    recurringInvoiceItemDao.batchCreateFromTransaction(recurringInvoiceItems);
 
-                    notifyOfFutureBillingEvents(invoiceSqlDao, invoiceItems);
-                    setChargedThroughDates(invoiceSqlDao, invoiceItems);
+                    notifyOfFutureBillingEvents(invoiceSqlDao, recurringInvoiceItems);
 
+                    List<InvoiceItem> fixedPriceInvoiceItems = invoice.getInvoiceItems(FixedPriceInvoiceItem.class);
+                    FixedPriceInvoiceItemSqlDao fixedPriceInvoiceItemDao = invoiceDao.become(FixedPriceInvoiceItemSqlDao.class);
+                    fixedPriceInvoiceItemDao.batchCreateFromTransaction(fixedPriceInvoiceItems);
+
+                    setChargedThroughDates(invoiceSqlDao, fixedPriceInvoiceItems, recurringInvoiceItems);
 
                     // STEPH Why do we need that? Are the payments not always null at this point?
                     List<InvoicePayment> invoicePayments = invoice.getPayments();
@@ -229,37 +248,71 @@ public class DefaultInvoiceDao implements InvoiceDao {
     }
 
     private void getInvoiceItemsWithinTransaction(final List<Invoice> invoices, final InvoiceSqlDao invoiceDao) {
-        InvoiceItemSqlDao invoiceItemDao = invoiceDao.become(InvoiceItemSqlDao.class);
         for (final Invoice invoice : invoices) {
-            List<InvoiceItem> invoiceItems = invoiceItemDao.getInvoiceItemsByInvoice(invoice.getId().toString());
-            invoice.addInvoiceItems(invoiceItems);
+            getInvoiceItemsWithinTransaction(invoice, invoiceDao);
         }
     }
 
+    private void getInvoiceItemsWithinTransaction(final Invoice invoice, final InvoiceSqlDao invoiceDao) {
+        RecurringInvoiceItemSqlDao recurringInvoiceItemDao = invoiceDao.become(RecurringInvoiceItemSqlDao.class);
+        List<InvoiceItem> recurringInvoiceItems = recurringInvoiceItemDao.getInvoiceItemsByInvoice(invoice.getId().toString());
+        invoice.addInvoiceItems(recurringInvoiceItems);
+
+        FixedPriceInvoiceItemSqlDao fixedPriceInvoiceItemDao = invoiceDao.become(FixedPriceInvoiceItemSqlDao.class);
+        List<InvoiceItem> fixedPriceInvoiceItems = fixedPriceInvoiceItemDao.getInvoiceItemsByInvoice(invoice.getId().toString());
+        invoice.addInvoiceItems(fixedPriceInvoiceItems);
+    }
+
     private void getInvoicePaymentsWithinTransaction(final List<Invoice> invoices, final InvoiceSqlDao invoiceDao) {
-        InvoicePaymentSqlDao invoicePaymentSqlDao = invoiceDao.become(InvoicePaymentSqlDao.class);
-        for (final Invoice invoice : invoices) {
-            String invoiceId = invoice.getId().toString();
-            List<InvoicePayment> invoicePayments = invoicePaymentSqlDao.getPaymentsForInvoice(invoiceId);
-            invoice.addPayments(invoicePayments);
+        for (Invoice invoice : invoices) {
+            getInvoicePaymentsWithinTransaction(invoice, invoiceDao);
         }
     }
 
+    private void getInvoicePaymentsWithinTransaction(final Invoice invoice, final InvoiceSqlDao invoiceDao) {
+        InvoicePaymentSqlDao invoicePaymentSqlDao = invoiceDao.become(InvoicePaymentSqlDao.class);
+        String invoiceId = invoice.getId().toString();
+        List<InvoicePayment> invoicePayments = invoicePaymentSqlDao.getPaymentsForInvoice(invoiceId);
+        invoice.addPayments(invoicePayments);
+    }
+
     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)) {
+                	nextBillingDatePoster.insertNextBillingNotification(dao, item.getSubscriptionId(), recurringInvoiceItem.getEndDate());
+                }
             }
         }
     }
+    
+    private void setChargedThroughDates(final InvoiceSqlDao dao, final Collection<InvoiceItem> fixedPriceItems,
+                                        final Collection<InvoiceItem> recurringItems) {
+        Map<UUID, DateTime> chargeThroughDates = new HashMap<UUID, DateTime>();
+        addInvoiceItemsToChargeThroughDates(chargeThroughDates, fixedPriceItems);
+        addInvoiceItemsToChargeThroughDates(chargeThroughDates, recurringItems);
+
+        for (UUID subscriptionId : chargeThroughDates.keySet()) {
+            DateTime chargeThroughDate = chargeThroughDates.get(subscriptionId);
+            log.info("Setting CTD for subscription {} to {}", subscriptionId.toString(), chargeThroughDate.toString());
+            entitlementBillingApi.setChargedThroughDateFromTransaction(dao, subscriptionId, chargeThroughDate);
+        }
+    }
 
-    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());
+    private void addInvoiceItemsToChargeThroughDates(Map<UUID, DateTime> chargeThroughDates, Collection<InvoiceItem> items) {
+        for (InvoiceItem item : items) {
+            UUID subscriptionId = item.getSubscriptionId();
+            DateTime endDate = item.getEndDate();
+
+            if (chargeThroughDates.containsKey(subscriptionId)) {
+                if (chargeThroughDates.get(subscriptionId).isBefore(endDate)) {
+                    chargeThroughDates.put(subscriptionId, endDate);
+                }
+            } else {
+                chargeThroughDates.put(subscriptionId, endDate);
             }
         }
     }
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..330784e
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.java
@@ -0,0 +1,114 @@
+/*
+ * 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.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<InvoiceItem> items);
+
+    @SqlBatch(transactional=false)
+    void batchCreateFromTransaction(@FixedPriceInvoiceItemBinder final List<InvoiceItem> 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("startDate", item.getStartDate().toDate());
+                        q.bind("endDate", item.getEndDate().toDate());
+                        q.bind("amount", item.getAmount());
+                        q.bind("currency", item.getCurrency().toString());
+                    }
+                };
+            }
+        }
+    }
+
+    public static class FixedPriceInvoiceItemMapper implements ResultSetMapper<InvoiceItem> {
+        @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 startDate = new DateTime(result.getTimestamp("start_date"));
+            DateTime endDate = new DateTime(result.getTimestamp("end_date"));
+            BigDecimal amount = result.getBigDecimal("amount");
+            Currency currency = Currency.valueOf(result.getString("currency"));
+
+            return new FixedPriceInvoiceItem(id, invoiceId, subscriptionId, planName, phaseName,
+                                            startDate, endDate, amount, currency);
+        }
+    }
+}
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/glue/InvoiceModule.java b/invoice/src/main/java/com/ning/billing/invoice/glue/InvoiceModule.java
index 372952b..26e0865 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/glue/InvoiceModule.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/glue/InvoiceModule.java
@@ -32,7 +32,9 @@ import com.ning.billing.invoice.dao.InvoiceDao;
 import com.ning.billing.invoice.model.DefaultInvoiceGenerator;
 import com.ning.billing.invoice.model.InvoiceGenerator;
 import com.ning.billing.invoice.notification.DefaultNextBillingDateNotifier;
+import com.ning.billing.invoice.notification.DefaultNextBillingDatePoster;
 import com.ning.billing.invoice.notification.NextBillingDateNotifier;
+import com.ning.billing.invoice.notification.NextBillingDatePoster;
 import com.ning.billing.util.glue.ClockModule;
 import com.ning.billing.util.glue.GlobalLockerModule;
 
@@ -65,6 +67,7 @@ public class InvoiceModule extends AbstractModule {
 
     protected void installNotifier() {
         bind(NextBillingDateNotifier.class).to(DefaultNextBillingDateNotifier.class).asEagerSingleton();
+        bind(NextBillingDatePoster.class).to(DefaultNextBillingDatePoster.class).asEagerSingleton();
     }
 
     protected void installGlobalLocker() {
diff --git a/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java
new file mode 100644
index 0000000..0a23d01
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java
@@ -0,0 +1,161 @@
+/*
+ * 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;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.entitlement.api.billing.BillingEvent;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+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.dao.InvoiceDao;
+import com.ning.billing.invoice.model.BillingEventSet;
+import com.ning.billing.invoice.model.InvoiceGenerator;
+import com.ning.billing.invoice.model.InvoiceItemList;
+import com.ning.billing.util.globallocker.GlobalLock;
+import com.ning.billing.util.globallocker.GlobalLocker;
+import com.ning.billing.util.globallocker.LockFailedException;
+import com.ning.billing.util.globallocker.GlobalLocker.LockerService;
+
+public class InvoiceDispatcher {
+	private final static Logger log = LoggerFactory.getLogger(InvoiceDispatcher.class);
+    private final static int NB_LOCK_TRY = 5;
+
+    private final InvoiceGenerator generator;
+    private final EntitlementBillingApi entitlementBillingApi;
+    private final AccountUserApi accountUserApi;
+    private final InvoiceDao invoiceDao;
+    private final GlobalLocker locker;
+
+    private final static boolean VERBOSE_OUTPUT = false;
+    @Inject
+    public InvoiceDispatcher(final InvoiceGenerator generator, final AccountUserApi accountUserApi,
+                           final EntitlementBillingApi entitlementBillingApi,
+                           final InvoiceDao invoiceDao,
+                           final GlobalLocker locker) {
+        this.generator = generator;
+        this.entitlementBillingApi = entitlementBillingApi;
+        this.accountUserApi = accountUserApi;
+        this.invoiceDao = invoiceDao;
+        this.locker = locker;
+    }
+
+
+    public 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());
+        log.info("Transition type: " + transition.getTransitionType().toString());
+        processSubscription(subscriptionId, targetDate);
+    }
+
+    public 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;
+        }
+
+        UUID accountId = entitlementBillingApi.getAccountIdFromSubscriptionId(subscriptionId);
+        if (accountId == null) {
+            log.error("Failed handling entitlement change.",
+                    new InvoiceApiException(ErrorCode.INVOICE_NO_ACCOUNT_ID_FOR_SUBSCRIPTION_ID, subscriptionId.toString()));
+            return;
+        }
+
+        GlobalLock lock = null;
+        try {
+            lock = locker.lockWithNumberOfTries(LockerService.INVOICE, accountId.toString(), NB_LOCK_TRY);
+
+            processAccountWithLock(accountId, targetDate);
+
+        } catch (LockFailedException e) {
+            // Not good!
+            log.error(String.format("Failed to process invoice for account %s, subscription %s, targetDate %s",
+                    accountId.toString(), subscriptionId.toString(), targetDate), e);
+        } finally {
+            if (lock != null) {
+                lock.release();
+            }
+        }
+    }
+
+    private void processAccountWithLock(final UUID accountId, final DateTime targetDate) throws InvoiceApiException {
+
+        Account account = accountUserApi.getAccountById(accountId);
+        if (account == null) {
+            log.error("Failed handling entitlement change.",
+                    new InvoiceApiException(ErrorCode.INVOICE_ACCOUNT_ID_INVALID, accountId.toString()));
+            return;
+        }
+
+        SortedSet<BillingEvent> events = entitlementBillingApi.getBillingEventsForAccount(accountId);
+        BillingEventSet billingEvents = new BillingEventSet(events);
+
+        Currency targetCurrency = account.getCurrency();
+
+        List<InvoiceItem> items = invoiceDao.getInvoiceItemsByAccount(accountId);
+        InvoiceItemList invoiceItemList = new InvoiceItemList(items);
+        Invoice invoice = generator.generateInvoice(accountId, billingEvents, invoiceItemList, targetDate, targetCurrency);
+
+        if (invoice == null) {
+            log.info("Generated null invoice.");
+            outputDebugData(events, invoiceItemList);
+        } else {
+            log.info("Generated invoice {} with {} items.", invoice.getId().toString(), invoice.getNumberOfItems());
+
+            if (VERBOSE_OUTPUT) {
+                log.info("New items");
+                for (InvoiceItem item : invoice.getInvoiceItems()) {
+                    log.info(item.toString());
+                }
+            }
+            outputDebugData(events, invoiceItemList);
+
+            if (invoice.getNumberOfItems() > 0) {
+                invoiceDao.create(invoice);
+            }
+        }
+    }
+
+    private void outputDebugData(Collection<BillingEvent> events, Collection<InvoiceItem> invoiceItemList) {
+        if (VERBOSE_OUTPUT) {
+            log.info("Events");
+            for (BillingEvent event : events) {
+                log.info(event.toString());
+            }
+
+            log.info("Existing items");
+            for (InvoiceItem item : invoiceItemList) {
+                log.info(item.toString());
+            }
+        }
+    }
+}
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 a86e063..140b106 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
@@ -16,9 +16,6 @@
 
 package com.ning.billing.invoice;
 
-import java.util.Collection;
-import java.util.List;
-import java.util.SortedSet;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
@@ -27,155 +24,34 @@ import org.slf4j.LoggerFactory;
 
 import com.google.common.eventbus.Subscribe;
 import com.google.inject.Inject;
-import com.ning.billing.ErrorCode;
-import com.ning.billing.account.api.Account;
-import com.ning.billing.account.api.AccountUserApi;
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.entitlement.api.billing.BillingEvent;
-import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
 import com.ning.billing.entitlement.api.user.SubscriptionTransition;
-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.dao.InvoiceDao;
-import com.ning.billing.invoice.model.BillingEventSet;
-import com.ning.billing.invoice.model.InvoiceGenerator;
-import com.ning.billing.invoice.model.InvoiceItemList;
-import com.ning.billing.invoice.notification.NextBillingDateEvent;
-import com.ning.billing.util.clock.Clock;
-import com.ning.billing.util.globalLocker.GlobalLock;
-import com.ning.billing.util.globalLocker.GlobalLocker;
-import com.ning.billing.util.globalLocker.GlobalLocker.LockerService;
-import com.ning.billing.util.globalLocker.LockFailedException;
 
 public class InvoiceListener {
-
-
     private final static Logger log = LoggerFactory.getLogger(InvoiceListener.class);
-    private final static int NB_LOCK_TRY = 5;
-
-    private final InvoiceGenerator generator;
-    private final EntitlementBillingApi entitlementBillingApi;
-    private final AccountUserApi accountUserApi;
-    private final InvoiceDao invoiceDao;
-    private final Clock clock;
-    private final  GlobalLocker locker;
-
-    private final static boolean VERBOSE_OUTPUT = false;
+	private InvoiceDispatcher dispatcher;
 
     @Inject
-    public InvoiceListener(final InvoiceGenerator generator, final AccountUserApi accountUserApi,
-                           final EntitlementBillingApi entitlementBillingApi,
-                           final InvoiceDao invoiceDao,
-                           final GlobalLocker locker,
-                           final Clock clock) {
-        this.generator = generator;
-        this.entitlementBillingApi = entitlementBillingApi;
-        this.accountUserApi = accountUserApi;
-        this.invoiceDao = invoiceDao;
-        this.locker = locker;
-        this.clock = clock;
+    public InvoiceListener(InvoiceDispatcher dispatcher) {
+        this.dispatcher = dispatcher;
     }
 
     @Subscribe
     public void handleSubscriptionTransition(final SubscriptionTransition transition) {
-        processSubscription(transition);
-    }
-
-    @Subscribe
-    public void handleNextBillingDateEvent(final NextBillingDateEvent event) {
-        // STEPH should we use the date of the event instead?
-        processSubscription(event.getSubscriptionId(), clock.getUTCNow());
-    }
-
-    private void processSubscription(final SubscriptionTransition transition) {
-        UUID subscriptionId = transition.getSubscriptionId();
-        DateTime targetDate = transition.getEffectiveTransitionTime();
-        log.info("Got subscription transition from InvoiceListener. id: " + subscriptionId.toString() + "; targetDate: " + targetDate.toString());
-        log.info("Transition type: " + transition.getTransitionType().toString());
-        processSubscription(subscriptionId, targetDate);
-    }
-
-    private void processSubscription(final UUID subscriptionId, final DateTime targetDate) {
-
-        if (subscriptionId == null) {
-            log.error("Failed handling entitlement change.", new InvoiceApiException(ErrorCode.INVOICE_INVALID_TRANSITION));
-            return;
-        }
-
-        UUID accountId = entitlementBillingApi.getAccountIdFromSubscriptionId(subscriptionId);
-        if (accountId == null) {
-            log.error("Failed handling entitlement change.",
-                    new InvoiceApiException(ErrorCode.INVOICE_NO_ACCOUNT_ID_FOR_SUBSCRIPTION_ID, subscriptionId.toString()));
-            return;
-        }
-
-        GlobalLock lock = null;
         try {
-            lock = locker.lockWithNumberOfTries(LockerService.INVOICE, accountId.toString(), NB_LOCK_TRY);
-
-            processAccountWithLock(accountId, targetDate);
-
-        } catch (LockFailedException e) {
-            // Not good!
-            log.error(String.format("Failed to process invoice for account %s, subscription %s, targetDate %s",
-                    accountId.toString(), subscriptionId.toString(), targetDate), e);
-        } finally {
-            if (lock != null) {
-                lock.release();
-            }
+        	dispatcher.processSubscription(transition);
+        } catch (InvoiceApiException e) {
+            log.error(e.getMessage());
         }
     }
 
-    private void processAccountWithLock(final UUID accountId, final DateTime targetDate) {
-
-        Account account = accountUserApi.getAccountById(accountId);
-        if (account == null) {
-            log.error("Failed handling entitlement change.",
-                    new InvoiceApiException(ErrorCode.INVOICE_ACCOUNT_ID_INVALID, accountId.toString()));
-            return;
-        }
-
-        SortedSet<BillingEvent> events = entitlementBillingApi.getBillingEventsForAccount(accountId);
-        BillingEventSet billingEvents = new BillingEventSet(events);
-
-        Currency targetCurrency = account.getCurrency();
-
-        List<InvoiceItem> items = invoiceDao.getInvoiceItemsByAccount(accountId);
-        InvoiceItemList invoiceItemList = new InvoiceItemList(items);
-        Invoice invoice = generator.generateInvoice(accountId, billingEvents, invoiceItemList, targetDate, targetCurrency);
-
-        if (invoice == null) {
-            log.info("Generated null invoice.");
-            outputDebugData(events, invoiceItemList);
-        } else {
-            log.info("Generated invoice {} with {} items.", invoice.getId().toString(), invoice.getNumberOfItems());
-
-            if (VERBOSE_OUTPUT) {
-                log.info("New items");
-                for (InvoiceItem item : invoice.getInvoiceItems()) {
-                    log.info(item.toString());
-                }
-            }
-            outputDebugData(events, invoiceItemList);
-
-            if (invoice.getNumberOfItems() > 0) {
-                invoiceDao.create(invoice);
-            }
+    public void handleNextBillingDateEvent(final UUID subscriptionId, final DateTime eventDateTime) {
+        try {
+        	dispatcher.processSubscription(subscriptionId, eventDateTime);
+        } catch (InvoiceApiException e) {
+            log.error(e.getMessage());
         }
     }
 
-    private void outputDebugData(Collection<BillingEvent> events, Collection<InvoiceItem> invoiceItemList) {
-        if (VERBOSE_OUTPUT) {
-            log.info("Events");
-            for (BillingEvent event : events) {
-                log.info(event.toString());
-            }
 
-            log.info("Existing items");
-            for (InvoiceItem item : invoiceItemList) {
-                log.info(item.toString());
-            }
-        }
-    }
 }
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..4920b88 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,9 @@ 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;
-
-    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);
+    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;
 }
\ 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/DefaultInvoice.java b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoice.java
index 8ee40e3..6d419de 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoice.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoice.java
@@ -20,6 +20,7 @@ import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.invoice.api.InvoicePayment;
+import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.DefaultClock;
 import org.joda.time.DateTime;
 
@@ -44,8 +45,8 @@ public class DefaultInvoice implements Invoice {
     private final DateTime targetDate;
     private final Currency currency;
 
-    public DefaultInvoice(UUID accountId, DateTime targetDate, Currency currency) {
-        this(UUID.randomUUID(), accountId, new DefaultClock().getUTCNow(), targetDate, currency);
+    public DefaultInvoice(UUID accountId, DateTime targetDate, Currency currency, Clock clock) {
+        this(UUID.randomUUID(), accountId, clock.getUTCNow(), targetDate, currency);
     }
 
     public DefaultInvoice(UUID invoiceId, UUID accountId, DateTime invoiceDate, DateTime targetDate,
@@ -73,6 +74,17 @@ public class DefaultInvoice implements Invoice {
     }
 
     @Override
+    public List<InvoiceItem> getInvoiceItems(Class clazz) {
+        List<InvoiceItem> results = new ArrayList<InvoiceItem>();
+        for (InvoiceItem item : invoiceItems) {
+            if (item.getClass() == clazz) {
+                results.add(item);
+            }
+        }
+        return results;
+    }
+
+    @Override
     public int getNumberOfItems() {
         return invoiceItems.size();
     }
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..039f305 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,250 @@
 package com.ning.billing.invoice.model;
 
 import java.math.BigDecimal;
+import java.util.ArrayList;
 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.google.inject.Inject;
+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.Duration;
+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 com.ning.billing.util.clock.Clock;
 
 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);
+
+    private final Clock clock;
+
+    @Inject
+    public DefaultInvoiceGenerator(Clock clock) {
+        this.clock = clock;
+    }
 
     @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 = new ArrayList<InvoiceItem>();
+        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);
+        DefaultInvoice invoice = new DefaultInvoice(accountId, targetDate, targetCurrency, clock);
+        UUID invoiceId = invoice.getId();
+        List<InvoiceItem> proposedItems = generateInvoiceItems(invoiceId, events, targetDate, targetCurrency);
+
+        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 (itemsToPost.size() == 0) {
+        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();
+
+            Iterator<InvoiceItem> existingItemIterator = existingInvoiceItems.iterator();
+            while (existingItemIterator.hasNext()) {
+                InvoiceItem existingItem = existingItemIterator.next();
+                if (existingItem.equals(proposedItem)) {
+                    existingItemIterator.remove();
+                    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);
-
-    		BigDecimal numberOfBillingPeriods;
-            BigDecimal recurringAmount = null;
-            DateTime billThroughDate;
-
-            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());
-            }
-
-            BigDecimal effectiveFixedPrice = items.hasInvoiceItemForPhase(event.getPlanPhase().getName()) ? null : fixedPrice;
-
-            // 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 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);
         }
-    }
 
-    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());
-            }
+        BillingPeriod billingPeriod = thisEvent.getBillingPeriod();
+        if (billingPeriod != BillingPeriod.NO_BILLING_PERIOD) {
+            BillingMode billingMode = instantiateBillingMode(thisEvent.getBillingMode());
+            DateTime startDate = thisEvent.getEffectiveDate();
+            if (!startDate.isAfter(targetDate)) {
+                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 (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);
+                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);
+                    }
+                }
             }
-    	} 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);
         }
-    }
 
-    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);
+        return items;
     }
 
-    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;
+    private BillingMode instantiateBillingMode(BillingModeType billingMode) {
+        switch (billingMode) {
+            case IN_ADVANCE:
+                return new InAdvanceBillingMode();
+            default:
+                throw new UnsupportedOperationException();
         }
     }
 
-    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 endDate = secondEvent.getEffectiveDate();
+    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 {
+                    Duration duration = thisEvent.getPlanPhase().getDuration();
+                    DateTime endDate = duration.addToDateTime(thisEvent.getEffectiveDate());
+                    BigDecimal fixedPrice = thisEvent.getFixedPrice().getPrice(currency);
+                    fixedPriceInvoiceItem = new FixedPriceInvoiceItem(invoiceId, thisEvent.getSubscription().getId(),
+                                                                      thisEvent.getPlan().getName(), thisEvent.getPlanPhase().getName(),
+                                                                      thisEvent.getEffectiveDate(), endDate, fixedPrice, currency);
+                } catch (CatalogApiException e) {
+                    throw new InvoiceApiException(e, ErrorCode.CAT_NO_PRICE_FOR_CURRENCY, currency.toString());
+                }
+            }
 
-        try {
-            return billingMode.calculateNumberOfBillingCycles(startDate, endDate, targetDate, billingCycleDay, billingPeriod);
-        } catch (InvalidDateSequenceException e) {
-            // TODO: Jeff -- log issue
-            return BigDecimal.ZERO;
+            return fixedPriceInvoiceItem;
         }
     }
 
-    private BillingMode getBillingMode(final BillingModeType billingModeType) {
-        switch (billingModeType) {
-            case IN_ADVANCE:
-                return new InAdvanceBillingMode();
-            default:
-                return null;
-        }
-    }
+//    // 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();
+//
+//        DateTime nextBillingDate;
+//
+//        // 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();
+//
+//            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();
+//            }
+//        }
+//
+//        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..2265abd
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
@@ -0,0 +1,121 @@
+/*
+* 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 extends InvoiceItemBase {
+    public FixedPriceInvoiceItem(UUID invoiceId, UUID subscriptionId, String planName, String phaseName, DateTime startDate, DateTime endDate, BigDecimal amount, Currency currency) {
+        super(invoiceId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency);
+    }
+
+    public FixedPriceInvoiceItem(UUID id, UUID invoiceId, UUID subscriptionId, String planName, String phaseName, DateTime startDate, DateTime endDate, BigDecimal amount, Currency currency) {
+        super(id, invoiceId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency);
+    }
+
+    @Override
+    public InvoiceItem asCredit() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getDescription() {
+        return String.format("%s (fixed price) on %s", getPhaseName(), getStartDate().toString());
+    }
+
+    @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 + (startDate != null ? startDate.hashCode() : 0);
+        result = 31 * result + (endDate != null ? endDate.hashCode() : 0);
+        result = 31 * result + (amount != null ? amount.hashCode() : 0);
+        result = 31 * result + (currency != null ? currency.hashCode() : 0);
+        return result;
+    }
+
+    @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 getStartDate().compareTo(that.getStartDate());
+        } else {
+            return compareSubscriptions;
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append(phaseName).append(", ");
+        sb.append(startDate.toString()).append(", ");
+        sb.append(endDate.toString()).append(", ");
+        sb.append(amount.toString()).append(", ");
+
+        return sb.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("startDate = ").append(startDate.toString()).append(", ");
+//        sb.append("endDate = ").append(endDate.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.compareTo(that.amount) != 0 : that.amount != null) return false;
+        if (currency != that.currency) return false;
+        if (startDate != null ? startDate.compareTo(that.startDate) != 0 : that.startDate != null) return false;
+        if (endDate != null ? endDate.compareTo(that.endDate) != 0 : that.endDate != 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..4d3dff9 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,126 @@ 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 {
+        List<RecurringInvoiceItemData> results = new ArrayList<RecurringInvoiceItemData>();
+
+        if (targetDate.isBefore(startDate)) {
+            // since the target date is before the start date of the event, this should result in no items being generated
+            throw new InvalidDateSequenceException();
+        }
 
-        if (!proposedDate.isBefore(date)) {return previousBillCycleDate;}
+        // beginning from the start date, find the first billing date
+        DateTime firstBillingCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
 
-        while (proposedDate.isBefore(date)) {
-            proposedDate = proposedDate.plusMonths(billingPeriod.getNumberOfMonths());
+        // 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 +152,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/InvoiceItemBase.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemBase.java
new file mode 100644
index 0000000..45aa26d
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemBase.java
@@ -0,0 +1,109 @@
+/*
+ * 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 abstract class InvoiceItemBase implements InvoiceItem {
+    protected final UUID id;
+    protected final UUID invoiceId;
+    protected final UUID subscriptionId;
+    protected final String planName;
+    protected final String phaseName;
+    protected final DateTime startDate;
+    protected final DateTime endDate;
+    protected final BigDecimal amount;
+    protected final Currency currency;
+
+    public InvoiceItemBase(UUID invoiceId, UUID subscriptionId, String planName, String phaseName,
+                           DateTime startDate, DateTime endDate, BigDecimal amount, Currency currency) {
+        this(UUID.randomUUID(), invoiceId, subscriptionId, planName, phaseName,
+             startDate, endDate, amount, currency);
+    }
+
+    public InvoiceItemBase(UUID id, UUID invoiceId, UUID subscriptionId, String planName, String phaseName,
+                           DateTime startDate, DateTime endDate, BigDecimal amount, Currency currency) {
+        this.id = id;
+        this.invoiceId = invoiceId;
+        this.subscriptionId = subscriptionId;
+        this.planName = planName;
+        this.phaseName = phaseName;
+        this.startDate = startDate;
+        this.endDate = endDate;
+        this.amount = amount;
+        this.currency = currency;
+    }
+
+    @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 BigDecimal getAmount() {
+        return amount;
+    }
+
+    @Override
+    public DateTime getStartDate() {
+        return startDate;
+    }
+
+    @Override
+    public DateTime getEndDate() {
+        return endDate;
+    }
+
+    @Override
+    public Currency getCurrency() {
+        return currency;
+    }
+
+    @Override
+    public abstract InvoiceItem asCredit();
+
+    @Override
+    public abstract String getDescription();
+
+    @Override
+    public abstract int compareTo(InvoiceItem invoiceItem);
+}
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/RecurringInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java
new file mode 100644
index 0000000..93ef474
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java
@@ -0,0 +1,188 @@
+/*
+ * 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 RecurringInvoiceItem extends InvoiceItemBase {
+    private final BigDecimal rate;
+    private final UUID reversedItemId;
+
+    public RecurringInvoiceItem(UUID invoiceId, UUID subscriptionId, String planName, String phaseName,
+                                DateTime startDate, DateTime endDate,
+                                BigDecimal amount, BigDecimal rate,
+                                Currency currency) {
+        this(UUID.randomUUID(), invoiceId, subscriptionId, planName, phaseName, startDate, endDate,
+             amount, rate, currency);
+    }
+
+    public RecurringInvoiceItem(UUID invoiceId, UUID subscriptionId, String planName, String phaseName,
+                                DateTime startDate, DateTime endDate,
+                                BigDecimal amount, BigDecimal rate,
+                                Currency currency, UUID reversedItemId) {
+        this(UUID.randomUUID(), invoiceId, subscriptionId, planName, phaseName, startDate, endDate,
+             amount, rate, currency, reversedItemId);
+    }
+
+    public RecurringInvoiceItem(UUID id, UUID invoiceId, UUID subscriptionId, String planName, String phaseName,
+                                DateTime startDate, DateTime endDate,
+                                BigDecimal amount, BigDecimal rate,
+                                Currency currency) {
+        super(id, invoiceId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency);
+
+        this.rate = rate;
+        this.reversedItemId = null;
+    }
+
+    public RecurringInvoiceItem(UUID id, UUID invoiceId, UUID subscriptionId, String planName, String phaseName,
+                                DateTime startDate, DateTime endDate,
+                                BigDecimal amount, BigDecimal rate,
+                                Currency currency, UUID reversedItemId) {
+        super(id, invoiceId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency);
+
+        this.rate = rate;
+        this.reversedItemId = reversedItemId;
+    }
+
+    @Override
+    public InvoiceItem asCredit() {
+        BigDecimal amountNegated = amount == null ? null : amount.negate();
+        return new RecurringInvoiceItem(invoiceId, subscriptionId, planName, phaseName, startDate, endDate,
+                                        amountNegated, rate, currency, id);
+    }
+
+    @Override
+    public String getDescription() {
+        return String.format("%s from %s to %s", phaseName, startDate.toString(), endDate.toString());
+    }
+
+    public UUID getReversedItemId() {
+        return reversedItemId;
+    }
+
+    public boolean reversesItem() {
+        return (reversedItemId != null);
+    }
+
+    public BigDecimal getRate() {
+        return rate;
+    }
+
+    @Override
+    public int compareTo(InvoiceItem item) {
+        if (item == null) {return -1;}
+        if (!(item instanceof RecurringInvoiceItem)) {return -1;}
+
+        RecurringInvoiceItem that = (RecurringInvoiceItem) item;
+
+        int compareSubscriptions = getSubscriptionId().compareTo(that.getSubscriptionId());
+        if (compareSubscriptions == 0) {
+            int compareStartDates = getStartDate().compareTo(that.getStartDate());
+            if (compareStartDates == 0) {
+                return getEndDate().compareTo(that.getEndDate());
+            } else {
+                return compareStartDates;
+            }
+        } else {
+            return compareSubscriptions;
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        RecurringInvoiceItem that = (RecurringInvoiceItem) o;
+
+        if (amount.compareTo(that.amount) != 0) return false;
+        if (currency != that.currency) return false;
+        if (startDate.compareTo(that.startDate) != 0) return false;
+        if (endDate.compareTo(that.endDate) != 0) return false;
+        if (!phaseName.equals(that.phaseName)) return false;
+        if (!planName.equals(that.planName)) return false;
+        if (rate.compareTo(that.rate) != 0) return false;
+        if (reversedItemId != null ? !reversedItemId.equals(that.reversedItemId) : that.reversedItemId != null)
+            return false;
+        if (!subscriptionId.equals(that.subscriptionId)) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = invoiceId.hashCode();
+        result = 31 * result + subscriptionId.hashCode();
+        result = 31 * result + planName.hashCode();
+        result = 31 * result + phaseName.hashCode();
+        result = 31 * result + startDate.hashCode();
+        result = 31 * result + endDate.hashCode();
+        result = 31 * result + amount.hashCode();
+        result = 31 * result + rate.hashCode();
+        result = 31 * result + currency.hashCode();
+        result = 31 * result + (reversedItemId != null ? reversedItemId.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append(phaseName).append(", ");
+        sb.append(startDate.toString()).append(", ");
+        sb.append(endDate.toString()).append(", ");
+        sb.append(amount.toString()).append(", ");
+
+        return sb.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("startDate = ").append(startDate.toString()).append(", ");
+//        if (endDate != null) {
+//            sb.append("endDate = ").append(endDate.toString()).append(", ");
+//        } else {
+//            sb.append("endDate = null");
+//        }
+//        sb.append("recurringAmount = ");
+//        if (amount == null) {
+//            sb.append("null");
+//        } else {
+//            sb.append(amount.toString());
+//        }
+//        sb.append(", ");
+//
+//        sb.append("recurringRate = ");
+//        if (rate == null) {
+//            sb.append("null");
+//        } else {
+//            sb.append(rate.toString());
+//        }
+//        sb.append(", ");
+//
+//        sb.append("}");
+//        return sb.toString();
+    }
+}
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..d42c533
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItemData.java
@@ -0,0 +1,45 @@
+/*
+ * 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 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/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java b/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java
index 3fd03b2..0dd5e3a 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java
@@ -18,8 +18,6 @@ package com.ning.billing.invoice.notification;
 
 import java.util.UUID;
 
-import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.entitlement.engine.dao.EntitlementDao;
 import org.joda.time.DateTime;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 import org.slf4j.Logger;
@@ -27,9 +25,11 @@ import org.slf4j.LoggerFactory;
 
 import com.google.inject.Inject;
 import com.ning.billing.config.InvoiceConfig;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import com.ning.billing.invoice.InvoiceListener;
 import com.ning.billing.invoice.api.DefaultInvoiceService;
 import com.ning.billing.util.bus.Bus;
-import com.ning.billing.util.bus.Bus.EventBusException;
 import com.ning.billing.util.notificationq.NotificationConfig;
 import com.ning.billing.util.notificationq.NotificationKey;
 import com.ning.billing.util.notificationq.NotificationQueue;
@@ -41,22 +41,22 @@ public class DefaultNextBillingDateNotifier implements  NextBillingDateNotifier 
 
     private final static Logger log = LoggerFactory.getLogger(DefaultNextBillingDateNotifier.class);
 
-    private static final String NEXT_BILLING_DATE_NOTIFIER_QUEUE = "next-billing-date-queue";
+    public static final String NEXT_BILLING_DATE_NOTIFIER_QUEUE = "next-billing-date-queue";
 
-    private final Bus eventBus;
     private final NotificationQueueService notificationQueueService;
 	private final InvoiceConfig config;
     private final EntitlementDao entitlementDao;
 
     private NotificationQueue nextBillingQueue;
+	private InvoiceListener listener;
 
     @Inject
-	public DefaultNextBillingDateNotifier(NotificationQueueService notificationQueueService, Bus eventBus,
-                                          InvoiceConfig config, EntitlementDao entitlementDao){
+	public DefaultNextBillingDateNotifier(NotificationQueueService notificationQueueService, 
+			InvoiceConfig config, EntitlementDao entitlementDao, InvoiceListener listener){
 		this.notificationQueueService = notificationQueueService;
 		this.config = config;
-		this.eventBus = eventBus;
         this.entitlementDao = entitlementDao;
+        this.listener = listener; 
 	}
 
     @Override
@@ -66,15 +66,14 @@ public class DefaultNextBillingDateNotifier implements  NextBillingDateNotifier 
             		NEXT_BILLING_DATE_NOTIFIER_QUEUE,
                     new NotificationQueueHandler() {
                 @Override
-                public void handleReadyNotification(String notificationKey) {
-                	UUID subscriptionId;
+                public void handleReadyNotification(String notificationKey, DateTime eventDate) {
                 	try {
                  		UUID key = UUID.fromString(notificationKey);
                         Subscription subscription = entitlementDao.getSubscriptionFromId(key);
                         if (subscription == null) {
                             log.warn("Next Billing Date Notification Queue handled spurious notification (key: " + key + ")" );
                         } else {
-                            processEvent(key);
+                            processEvent(key , eventDate);
                         }
                 	} catch (IllegalArgumentException e) {
                 		log.error("The key returned from the NextBillingNotificationQueue is not a valid UUID", e);
@@ -118,27 +117,9 @@ public class DefaultNextBillingDateNotifier implements  NextBillingDateNotifier 
         }
     }
 
-    private void processEvent(UUID subscriptionId) {
-        try {
-            eventBus.post(new NextBillingDateEvent(subscriptionId));
-        } catch (EventBusException e) {
-            log.error("Failed to post entitlement event " + subscriptionId, e);
-        }
+    private void processEvent(UUID subscriptionId, DateTime eventDateTime) {
+        listener.handleNextBillingDateEvent(subscriptionId, eventDateTime);
     }
 
-    @Override
-    public void insertNextBillingNotification(final Transmogrifier transactionalDao, final UUID subscriptionId, final DateTime futureNotificationTime) {
-    	if (nextBillingQueue != null) {
-            log.info("Queuing next billing date notification. id: {}, timestamp: {}", subscriptionId.toString(), futureNotificationTime.toString());
-
-            nextBillingQueue.recordFutureNotificationFromTransaction(transactionalDao, futureNotificationTime, new NotificationKey(){
-                @Override
-                public String toString() {
-                    return subscriptionId.toString();
-                }
-    	    });
-        } else {
-            log.error("Attempting to put items on a non-existent queue (NextBillingDateNotifier).");
-        }
-    }
+ 
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDateNotifier.java b/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDateNotifier.java
index d33dc61..ea630aa 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDateNotifier.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDateNotifier.java
@@ -16,10 +16,6 @@
 
 package com.ning.billing.invoice.notification;
 
-import java.util.UUID;
-
-import org.joda.time.DateTime;
-import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 
 public interface NextBillingDateNotifier {
 
@@ -29,7 +25,4 @@ public interface NextBillingDateNotifier {
 
     public void stop();
 
-	public void insertNextBillingNotification(Transmogrifier transactionalDao,
-			UUID subscriptionId, DateTime futureNotificationTime);
-
 }
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..90310d5
--- /dev/null
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.sql.stg
@@ -0,0 +1,63 @@
+group FixedPriceInvoiceItemSqlDao;
+
+fields(prefix) ::= <<
+  <prefix>id,
+  <prefix>invoice_id,
+  <prefix>subscription_id,
+  <prefix>plan_name,
+  <prefix>phase_name,
+  <prefix>start_date,
+  <prefix>end_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,
+         :startDate, :endDate, :amount, :currency);
+>>
+
+batchCreateFromTransaction() ::= <<
+  INSERT INTO fixed_invoice_items(<fields()>)
+  VALUES(:id, :invoiceId, :subscriptionId, :planName, :phaseName,
+         :startDate, :endDate, :amount, :currency);
+>>
+
+update() ::= <<
+  UPDATE fixed_invoice_items
+  SET invoice_id = :invoiceId, subscription_id = :subscriptionId, plan_name = :planName, phase_name = :phaseName,
+      start_date = :startDate, end_date = :endDate, 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 10beb12..35c45bf 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..c7712a8 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,30 @@ 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,
+  start_date datetime NOT NULL,
+  end_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 +74,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..4e07a39 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;
@@ -60,7 +61,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
     @Test
     public void testCreationAndRetrievalByAccount() {
         UUID accountId = UUID.randomUUID();
-        Invoice invoice = new DefaultInvoice(accountId, new DefaultClock().getUTCNow(), Currency.USD);
+        Invoice invoice = new DefaultInvoice(accountId, clock.getUTCNow(), Currency.USD, clock);
         DateTime invoiceDate = invoice.getInvoiceDate();
 
         invoiceDao.create(invoice);
@@ -79,12 +80,12 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
     @Test
     public void testInvoicePayment() {
         UUID accountId = UUID.randomUUID();
-        Invoice invoice = new DefaultInvoice(accountId, new DefaultClock().getUTCNow(), Currency.USD);
+        Invoice invoice = new DefaultInvoice(accountId, clock.getUTCNow(), Currency.USD, clock);
         UUID invoiceId = invoice.getId();
         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);
 
@@ -118,7 +119,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
     public void testAddPayment() {
         UUID accountId = UUID.randomUUID();
         DateTime targetDate = new DateTime(2011, 10, 6, 0, 0, 0, 0);
-        Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD);
+        Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD, clock);
 
         UUID paymentAttemptId = UUID.randomUUID();
         DateTime paymentAttemptDate = new DateTime(2011, 6, 24, 12, 14, 36, 0);
@@ -137,7 +138,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
     public void testAddPaymentAttempt() {
         UUID accountId = UUID.randomUUID();
         DateTime targetDate = new DateTime(2011, 10, 6, 0, 0, 0, 0);
-        Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD);
+        Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD, clock);
 
         DateTime paymentAttemptDate = new DateTime(2011, 6, 24, 12, 14, 36, 0);
 
@@ -158,7 +159,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         int existingInvoiceCount = invoices.size();
 
         UUID accountId = UUID.randomUUID();
-        Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD);
+        Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD, clock);
 
         invoiceDao.create(invoice);
         invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
@@ -173,7 +174,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         // create a new invoice with one item
         UUID accountId = UUID.randomUUID();
         DateTime targetDate = new DateTime(2011, 10, 6, 0, 0, 0, 0);
-        Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD);
+        Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD, clock);
 
         UUID invoiceId = invoice.getId();
         UUID subscriptionId = UUID.randomUUID();
@@ -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);
 
@@ -268,7 +269,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
 
 
         // create invoice 1 (subscriptions 1-4)
-        Invoice invoice1 = new DefaultInvoice(accountId, targetDate, Currency.USD);
+        Invoice invoice1 = new DefaultInvoice(accountId, targetDate, Currency.USD, clock);
         invoiceDao.create(invoice1);
 
         UUID invoiceId1 = invoice1.getId();
@@ -276,20 +277,20 @@ 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);
+        DefaultInvoice invoice2 = new DefaultInvoice(accountId, targetDate, Currency.USD, clock);
         invoiceDao.create(invoice2);
 
         UUID invoiceId2 = invoice2.getId();
@@ -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);
@@ -324,11 +325,11 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
     public void testGetInvoicesForAccountAfterDate() {
         UUID accountId = UUID.randomUUID();
         DateTime targetDate1 = new DateTime(2011, 10, 6, 0, 0, 0, 0);
-        Invoice invoice1 = new DefaultInvoice(accountId, targetDate1, Currency.USD);
+        Invoice invoice1 = new DefaultInvoice(accountId, targetDate1, Currency.USD, clock);
         invoiceDao.create(invoice1);
 
         DateTime targetDate2 = new DateTime(2011, 12, 6, 0, 0, 0, 0);
-        Invoice invoice2 = new DefaultInvoice(accountId, targetDate2, Currency.USD);
+        Invoice invoice2 = new DefaultInvoice(accountId, targetDate2, Currency.USD, clock);
         invoiceDao.create(invoice2);
 
 
@@ -353,7 +354,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
     public void testAccountBalance() {
         UUID accountId = UUID.randomUUID();
         DateTime targetDate1 = new DateTime(2011, 10, 6, 0, 0, 0, 0);
-        Invoice invoice1 = new DefaultInvoice(accountId, targetDate1, Currency.USD);
+        Invoice invoice1 = new DefaultInvoice(accountId, targetDate1, Currency.USD, clock);
         invoiceDao.create(invoice1);
 
         DateTime startDate = new DateTime(2011, 3, 1, 0, 0, 0, 0);
@@ -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);
@@ -380,7 +381,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
     public void testAccountBalanceWithNoPayments() {
         UUID accountId = UUID.randomUUID();
         DateTime targetDate1 = new DateTime(2011, 10, 6, 0, 0, 0, 0);
-        Invoice invoice1 = new DefaultInvoice(accountId, targetDate1, Currency.USD);
+        Invoice invoice1 = new DefaultInvoice(accountId, targetDate1, Currency.USD, clock);
         invoiceDao.create(invoice1);
 
         DateTime startDate = new DateTime(2011, 3, 1, 0, 0, 0, 0);
@@ -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);
@@ -403,7 +404,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
     public void testAccountBalanceWithNoInvoiceItems() {
         UUID accountId = UUID.randomUUID();
         DateTime targetDate1 = new DateTime(2011, 10, 6, 0, 0, 0, 0);
-        Invoice invoice1 = new DefaultInvoice(accountId, targetDate1, Currency.USD);
+        Invoice invoice1 = new DefaultInvoice(accountId, targetDate1, Currency.USD, clock);
         invoiceDao.create(invoice1);
 
         BigDecimal payment1 = new BigDecimal("48.0");
@@ -413,12 +414,12 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         BigDecimal balance = invoiceDao.getAccountBalance(accountId);
         assertEquals(balance.compareTo(BigDecimal.ZERO.subtract(payment1)), 0);
     }
-    
+
     @Test
     public void testGetUnpaidInvoicesByAccountId() {
         UUID accountId = UUID.randomUUID();
         DateTime targetDate1 = new DateTime(2011, 10, 6, 0, 0, 0, 0);
-        Invoice invoice1 = new DefaultInvoice(accountId, targetDate1, Currency.USD);
+        Invoice invoice1 = new DefaultInvoice(accountId, targetDate1, Currency.USD, clock);
         invoiceDao.create(invoice1);
 
         DateTime startDate = new DateTime(2011, 3, 1, 0, 0, 0, 0);
@@ -427,25 +428,25 @@ 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;
-        
+
         upToDate = new DateTime(2011, 1, 1, 0, 0, 0, 0);
         invoices = invoiceDao.getUnpaidInvoicesByAccountId(accountId, upToDate);
         assertEquals(invoices.size(), 0);
-        
+
         upToDate = new DateTime(2012, 1, 1, 0, 0, 0, 0);
         invoices = invoiceDao.getUnpaidInvoicesByAccountId(accountId, upToDate);
         assertEquals(invoices.size(), 1);
 
         DateTime targetDate2 = new DateTime(2011, 7, 1, 0, 0, 0, 0);
-        Invoice invoice2 = new DefaultInvoice(accountId, targetDate2, Currency.USD);
+        Invoice invoice2 = new DefaultInvoice(accountId, targetDate2, Currency.USD, clock);
         invoiceDao.create(invoice2);
 
         DateTime startDate2 = new DateTime(2011, 6, 1, 0, 0, 0, 0);
@@ -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,8 +472,9 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
      *
      */
     @Test
-    public void testInvoiceGenerationForImmediateChanges() {
-        InvoiceGenerator generator = new DefaultInvoiceGenerator();
+    public void testInvoiceGenerationForImmediateChanges() throws InvoiceApiException {
+
+        InvoiceGenerator generator = new DefaultInvoiceGenerator(clock);
 
         UUID accountId = UUID.randomUUID();
         InvoiceItemList invoiceItemList = new InvoiceItemList();
@@ -526,7 +528,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);
@@ -542,17 +544,185 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         events.add(event);
 
         DateTime targetDate = buildDateTime(2011, 1, 15);
-        InvoiceGenerator generator = new DefaultInvoiceGenerator();
+        InvoiceGenerator generator = new DefaultInvoiceGenerator(clock);
         Invoice invoice = generator.generateInvoice(UUID.randomUUID(), events, null, targetDate, Currency.USD);
-        assertEquals(invoice.getNumberOfItems(), 1);
+
+        // expect one pro-ration item and one full-period item
+        assertEquals(invoice.getNumberOfItems(), 2);
         assertEquals(invoice.getTotalAmount().compareTo(ZERO), 0);
     }
 
     @Test
-    public void testInvoiceForEmptyEventSet() {
-        InvoiceGenerator generator = new DefaultInvoiceGenerator();
+    public void testInvoiceForFreeTrialWithRecurringDiscount() throws InvoiceApiException {
+        DefaultPrice zeroPrice = new DefaultPrice(BigDecimal.ZERO, Currency.USD);
+        MockInternationalPrice fixedPrice = new MockInternationalPrice(zeroPrice);
+        MockPlanPhase phase1 = new MockPlanPhase(null, fixedPrice);
+
+        BigDecimal cheapAmount = new BigDecimal("24.95");
+        DefaultPrice cheapPrice = new DefaultPrice(cheapAmount, Currency.USD);
+        MockInternationalPrice recurringPrice = new MockInternationalPrice(cheapPrice);
+        MockPlanPhase phase2 = new MockPlanPhase(recurringPrice, null);
+
+        MockPlan plan = new MockPlan();
+        Subscription subscription = new MockSubscription();
+        DateTime effectiveDate1 = buildDateTime(2011, 1, 1);
+
+        BillingEvent event1 = new DefaultBillingEvent(subscription, effectiveDate1, plan, phase1, fixedPrice,
+                                                     null, BillingPeriod.MONTHLY, 1, BillingModeType.IN_ADVANCE,
+                                                     "testEvent1", SubscriptionTransitionType.CREATE);
+        BillingEventSet events = new BillingEventSet();
+        events.add(event1);
+
+        InvoiceGenerator generator = new DefaultInvoiceGenerator(clock);
+        Invoice invoice1 = generator.generateInvoice(UUID.randomUUID(), events, null, effectiveDate1, Currency.USD);
+        assertNotNull(invoice1);
+        assertEquals(invoice1.getNumberOfItems(), 1);
+        assertEquals(invoice1.getTotalAmount().compareTo(ZERO), 0);
+
+        List<InvoiceItem> existingItems = invoice1.getInvoiceItems();
+
+        DateTime effectiveDate2 = effectiveDate1.plusDays(30);
+        BillingEvent event2 = new DefaultBillingEvent(subscription, effectiveDate2, plan, phase2, null,
+                                                     recurringPrice, BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
+                                                     "testEvent2", SubscriptionTransitionType.CHANGE);
+        events.add(event2);
+
+        Invoice invoice2 = generator.generateInvoice(UUID.randomUUID(), events, existingItems, effectiveDate2, Currency.USD);
+        assertNotNull(invoice2);
+        assertEquals(invoice2.getNumberOfItems(), 1);
+        assertEquals(invoice2.getTotalAmount().compareTo(cheapAmount), 0);
+
+        existingItems.addAll(invoice2.getInvoiceItems());
+
+        DateTime effectiveDate3 = effectiveDate2.plusMonths(1);
+        Invoice invoice3 = generator.generateInvoice(UUID.randomUUID(), events, existingItems, effectiveDate3, Currency.USD);
+        assertNotNull(invoice3);
+        assertEquals(invoice3.getNumberOfItems(), 1);
+        assertEquals(invoice3.getTotalAmount().compareTo(cheapAmount), 0);
+    }
+
+    @Test
+    public void testInvoiceForEmptyEventSet() throws InvoiceApiException {
+        InvoiceGenerator generator = new DefaultInvoiceGenerator(clock);
         BillingEventSet events = new BillingEventSet();
         Invoice invoice = generator.generateInvoice(UUID.randomUUID(), events, null, new DateTime(), Currency.USD);
         assertNull(invoice);
     }
+
+    @Test
+    public void testMixedModeInvoicePersistence() throws InvoiceApiException {
+        DefaultPrice zeroPrice = new DefaultPrice(BigDecimal.ZERO, Currency.USD);
+        MockInternationalPrice fixedPrice = new MockInternationalPrice(zeroPrice);
+        MockPlanPhase phase1 = new MockPlanPhase(null, fixedPrice);
+
+        BigDecimal cheapAmount = new BigDecimal("24.95");
+        DefaultPrice cheapPrice = new DefaultPrice(cheapAmount, Currency.USD);
+        MockInternationalPrice recurringPrice = new MockInternationalPrice(cheapPrice);
+        MockPlanPhase phase2 = new MockPlanPhase(recurringPrice, null);
+
+        MockPlan plan = new MockPlan();
+        Subscription subscription = new MockSubscription();
+        DateTime effectiveDate1 = buildDateTime(2011, 1, 1);
+
+        BillingEvent event1 = new DefaultBillingEvent(subscription, effectiveDate1, plan, phase1, fixedPrice,
+                                                     null, BillingPeriod.MONTHLY, 1, BillingModeType.IN_ADVANCE,
+                                                     "testEvent1", SubscriptionTransitionType.CREATE);
+        BillingEventSet events = new BillingEventSet();
+        events.add(event1);
+
+        DateTime effectiveDate2 = effectiveDate1.plusDays(30);
+        BillingEvent event2 = new DefaultBillingEvent(subscription, effectiveDate2, plan, phase2, null,
+                                                     recurringPrice, BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
+                                                     "testEvent2", SubscriptionTransitionType.CHANGE);
+        events.add(event2);
+
+        InvoiceGenerator generator = new DefaultInvoiceGenerator(clock);
+        Invoice invoice = generator.generateInvoice(UUID.randomUUID(), events, null, effectiveDate2, Currency.USD);
+        assertNotNull(invoice);
+        assertEquals(invoice.getNumberOfItems(), 2);
+        assertEquals(invoice.getTotalAmount().compareTo(cheapAmount), 0);
+
+        invoiceDao.create(invoice);
+        Invoice savedInvoice = invoiceDao.getById(invoice.getId());
+
+        assertNotNull(savedInvoice);
+        assertEquals(savedInvoice.getNumberOfItems(), 2);
+        assertEquals(savedInvoice.getTotalAmount().compareTo(cheapAmount), 0);
+    }
+
+//    @Test
+//    public void testCancellationWithMultipleBillingPeriodsFollowing() throws InvoiceApiException {
+//        UUID accountId = UUID.randomUUID();
+//
+//        BigDecimal fixedValue = FIVE;
+//        DefaultPrice fixedAmount = new DefaultPrice(fixedValue, Currency.USD);
+//        MockInternationalPrice fixedPrice = new MockInternationalPrice(fixedAmount);
+//        MockPlanPhase plan1phase1 = new MockPlanPhase(null, fixedPrice);
+//
+//        BigDecimal trialValue = new BigDecimal("9.95");
+//        DefaultPrice trialAmount = new DefaultPrice(trialValue, Currency.USD);
+//        MockInternationalPrice trialPrice = new MockInternationalPrice(trialAmount);
+//        MockPlanPhase plan2phase1 = new MockPlanPhase(trialPrice, null);
+//
+//        BigDecimal discountValue = new BigDecimal("24.95");
+//        DefaultPrice discountAmount = new DefaultPrice(discountValue, Currency.USD);
+//        MockInternationalPrice discountPrice = new MockInternationalPrice(discountAmount);
+//        MockPlanPhase plan2phase2 = new MockPlanPhase(discountPrice, null);
+//
+//        MockPlan plan1 = new MockPlan();
+//        MockPlan plan2 = new MockPlan();
+//        Subscription subscription = new MockSubscription();
+//        DateTime effectiveDate1 = buildDateTime(2011, 1, 1);
+//
+//        BillingEvent creationEvent = new DefaultBillingEvent(subscription, effectiveDate1, plan1, plan1phase1, fixedPrice,
+//                                                     null, BillingPeriod.MONTHLY, 1, BillingModeType.IN_ADVANCE,
+//                                                     "trial", SubscriptionTransitionType.CREATE);
+//        BillingEventSet events = new BillingEventSet();
+//        events.add(creationEvent);
+//
+//        InvoiceGenerator generator = new DefaultInvoiceGenerator();
+//        InvoiceItemList existingItems;
+//
+//        existingItems = new InvoiceItemList(invoiceDao.getInvoiceItemsByAccount(accountId));
+//        Invoice invoice1 = generator.generateInvoice(accountId, events, existingItems, effectiveDate1, Currency.USD);
+//
+//        assertNotNull(invoice1);
+//        assertEquals(invoice1.getNumberOfItems(), 1);
+//        assertEquals(invoice1.getTotalAmount().compareTo(fixedValue), 0);
+//        invoiceDao.create(invoice1);
+//
+//        DateTime effectiveDate2 = effectiveDate1.plusSeconds(1);
+//        BillingEvent changeEvent = new DefaultBillingEvent(subscription, effectiveDate2, plan2, plan2phase1, null,
+//                                                     trialPrice, BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
+//                                                     "discount", SubscriptionTransitionType.CHANGE);
+//        events.add(changeEvent);
+//
+//        existingItems = new InvoiceItemList(invoiceDao.getInvoiceItemsByAccount(accountId));
+//        Invoice invoice2 = generator.generateInvoice(accountId, events, existingItems, effectiveDate2, Currency.USD);
+//        assertNotNull(invoice2);
+//        assertEquals(invoice2.getNumberOfItems(), 2);
+//        assertEquals(invoice2.getTotalAmount().compareTo(trialValue), 0);
+//        invoiceDao.create(invoice2);
+//
+//        DateTime effectiveDate3 = effectiveDate2.plusMonths(1);
+//        BillingEvent phaseEvent = new DefaultBillingEvent(subscription, effectiveDate3, plan2, plan2phase2, null,
+//                                                     discountPrice, BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
+//                                                     "discount", SubscriptionTransitionType.PHASE);
+//        events.add(phaseEvent);
+//
+//        existingItems = new InvoiceItemList(invoiceDao.getInvoiceItemsByAccount(accountId));
+//        Invoice invoice3 = generator.generateInvoice(accountId, events, existingItems, effectiveDate3, Currency.USD);
+//        assertNotNull(invoice3);
+//        assertEquals(invoice3.getNumberOfItems(), 1);
+//        assertEquals(invoice3.getTotalAmount().compareTo(discountValue), 0);
+//        invoiceDao.create(invoice3);
+//
+//        DateTime effectiveDate4 = effectiveDate3.plusMonths(1);
+//        existingItems = new InvoiceItemList(invoiceDao.getInvoiceItemsByAccount(accountId));
+//        Invoice invoice4 = generator.generateInvoice(accountId, events, existingItems, effectiveDate4, Currency.USD);
+//        assertNotNull(invoice4);
+//        assertEquals(invoice4.getNumberOfItems(), 1);
+//        assertEquals(invoice4.getTotalAmount().compareTo(discountValue), 0);
+//        invoiceDao.create(invoice4);
+//    }
 }
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..16e95fb 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,27 +16,28 @@
 
 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 com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.DefaultClock;
+
 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 {
+
+    private final Clock clock = new DefaultClock();
+
     @Test
     public void testInvoiceItemCreation() {
         UUID invoiceId = UUID.randomUUID();
@@ -45,19 +46,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 +69,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 +86,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);
     }
 
@@ -98,7 +98,7 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
     public void testGetInvoiceItemsByAccountId() {
         UUID accountId = UUID.randomUUID();
         DateTime targetDate = new DateTime(2011, 5, 23, 0, 0, 0, 0);
-        DefaultInvoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD);
+        DefaultInvoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD, clock);
 
         invoiceDao.create(invoice);
 
@@ -107,10 +107,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/dao/MockSubscription.java b/invoice/src/test/java/com/ning/billing/invoice/dao/MockSubscription.java
index 005a605..7a9253c 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/MockSubscription.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/MockSubscription.java
@@ -28,7 +28,7 @@ import java.util.List;
 import java.util.UUID;
 
 public class MockSubscription implements Subscription {
-    private UUID subscriptionId = UUID.randomUUID();
+    private final UUID subscriptionId = UUID.randomUUID();
 
     @Override
     public void cancel(DateTime requestedDate, boolean eot) throws EntitlementUserApiException {
@@ -119,4 +119,9 @@ public class MockSubscription implements Subscription {
     public SubscriptionTransition getPendingTransition() {
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public SubscriptionTransition getPreviousTransition() {
+        return null;
+    }
 }
\ No newline at end of file
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 b252531..4ab3495 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,13 @@ 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.invoice.dao.RecurringInvoiceItemSqlDao;
 import com.ning.billing.util.glue.GlobalLockerModule;
 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 +50,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/glue/InvoiceModuleWithMocks.java b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java
index e5e699b..01f0eb2 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java
@@ -18,8 +18,9 @@ package com.ning.billing.invoice.glue;
 
 import com.ning.billing.invoice.dao.InvoiceDao;
 import com.ning.billing.invoice.dao.MockInvoiceDao;
-import com.ning.billing.util.globalLocker.GlobalLocker;
-import com.ning.billing.util.globalLocker.MockGlobalLocker;
+import com.ning.billing.util.globallocker.GlobalLocker;
+import com.ning.billing.util.globallocker.MockGlobalLocker;
+
 
 public class InvoiceModuleWithMocks extends InvoiceModule {
     @Override
diff --git a/invoice/src/test/java/com/ning/billing/invoice/notification/BrainDeadSubscription.java b/invoice/src/test/java/com/ning/billing/invoice/notification/BrainDeadSubscription.java
new file mode 100644
index 0000000..587144b
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/notification/BrainDeadSubscription.java
@@ -0,0 +1,149 @@
+/*
+ * 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.notification;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+
+public class BrainDeadSubscription implements Subscription {
+
+	@Override
+	public void cancel(DateTime requestedDate, boolean eot)
+			throws EntitlementUserApiException {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public void uncancel() throws EntitlementUserApiException {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public void changePlan(String productName, BillingPeriod term,
+			String planSet, DateTime requestedDate)
+			throws EntitlementUserApiException {
+		throw new UnsupportedOperationException();
+
+		
+	}
+
+	@Override
+	public void pause() throws EntitlementUserApiException {
+		throw new UnsupportedOperationException();
+
+		
+	}
+
+	@Override
+	public void resume() throws EntitlementUserApiException {
+		throw new UnsupportedOperationException();
+		
+	}
+
+	@Override
+	public UUID getId() {
+		throw new UnsupportedOperationException();
+		
+	}
+
+	@Override
+	public UUID getBundleId() {
+		throw new UnsupportedOperationException();
+		
+	}
+
+	@Override
+	public SubscriptionState getState() {
+		throw new UnsupportedOperationException();
+		
+	}
+
+	@Override
+	public DateTime getStartDate() {
+		throw new UnsupportedOperationException();
+		
+	}
+
+	@Override
+	public DateTime getEndDate() {
+		throw new UnsupportedOperationException();
+		
+	}
+
+	@Override
+	public Plan getCurrentPlan() {
+		throw new UnsupportedOperationException();
+		
+	}
+
+	@Override
+	public String getCurrentPriceList() {
+		throw new UnsupportedOperationException();
+		
+	}
+
+	@Override
+	public PlanPhase getCurrentPhase() {
+		throw new UnsupportedOperationException();
+		
+	}
+
+	@Override
+	public DateTime getChargedThroughDate() {
+		throw new UnsupportedOperationException();
+		
+	}
+
+	@Override
+	public DateTime getPaidThroughDate() {
+		throw new UnsupportedOperationException();
+		
+	}
+
+	@Override
+	public List<SubscriptionTransition> getActiveTransitions() {
+		throw new UnsupportedOperationException();
+		
+	}
+
+	@Override
+	public List<SubscriptionTransition> getAllTransitions() {
+		throw new UnsupportedOperationException();
+		
+	}
+
+	@Override
+	public SubscriptionTransition getPendingTransition() {
+		throw new UnsupportedOperationException();
+		
+	}
+
+	@Override
+	public SubscriptionTransition getPreviousTransition() {
+		throw new UnsupportedOperationException();
+	}
+
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java b/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java
index d2fcdee..67c27fa 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java
@@ -16,17 +16,15 @@
 
 package com.ning.billing.invoice.notification;
 
+import static com.jayway.awaitility.Awaitility.await;
+import static java.util.concurrent.TimeUnit.MINUTES;
+
 import java.io.IOException;
 import java.sql.SQLException;
+import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.Callable;
 
-import com.ning.billing.catalog.DefaultCatalogService;
-import com.ning.billing.catalog.api.CatalogService;
-import com.ning.billing.config.CatalogConfig;
-import com.ning.billing.entitlement.engine.dao.EntitlementDao;
-import com.ning.billing.entitlement.engine.dao.EntitlementSqlDao;
-import com.ning.billing.util.bus.InMemoryBus;
 import org.apache.commons.io.IOUtils;
 import org.joda.time.DateTime;
 import org.skife.config.ConfigurationObjectFactory;
@@ -38,25 +36,36 @@ import org.slf4j.LoggerFactory;
 import org.testng.Assert;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
-import com.google.common.eventbus.Subscribe;
+
 import com.google.inject.AbstractModule;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
+import com.ning.billing.catalog.DefaultCatalogService;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.config.CatalogConfig;
 import com.ning.billing.config.InvoiceConfig;
 import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.api.migration.AccountMigrationData;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import com.ning.billing.entitlement.engine.dao.EntitlementSqlDao;
+import com.ning.billing.entitlement.events.EntitlementEvent;
+import com.ning.billing.invoice.InvoiceListener;
+import com.ning.billing.invoice.dao.DefaultInvoiceDao;
 import com.ning.billing.lifecycle.KillbillService.ServiceException;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.InMemoryBus;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.ClockMock;
-import com.ning.billing.util.bus.Bus;
 import com.ning.billing.util.notificationq.DefaultNotificationQueueService;
 import com.ning.billing.util.notificationq.DummySqlTest;
 import com.ning.billing.util.notificationq.NotificationQueueService;
 import com.ning.billing.util.notificationq.dao.NotificationSqlDao;
 
-import static com.jayway.awaitility.Awaitility.await;
-import static java.util.concurrent.TimeUnit.MINUTES;
-
 public class TestNextBillingDateNotifier {
     private static Logger log = LoggerFactory.getLogger(TestNextBillingDateNotifier.class);
 	private Clock clock;
@@ -64,7 +73,159 @@ public class TestNextBillingDateNotifier {
 	private DummySqlTest dao;
 	private Bus eventBus;
 	private MysqlTestingHelper helper;
+	private InvoiceListenerMock listener = new InvoiceListenerMock();
+	private NotificationQueueService notificationQueueService;
+
+	private static final class InvoiceListenerMock extends InvoiceListener {
+		int eventCount = 0;
+		UUID latestSubscriptionId = null;
+
+		public InvoiceListenerMock() {
+			super(null);
+		}
+		
+
+		@Override
+		public void handleNextBillingDateEvent(UUID subscriptionId,
+				DateTime eventDateTime) {
+			eventCount++;
+			latestSubscriptionId=subscriptionId;
+		}
+		
+		public int getEventCount() {
+			return eventCount;
+		}
+		
+		public UUID getLatestSubscriptionId(){
+			return latestSubscriptionId;
+		}
+		
+	}
+	
+	private class MockEntitlementDao implements EntitlementDao {
+
+		@Override
+		public List<SubscriptionBundle> getSubscriptionBundleForAccount(
+				UUID accountId) {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public SubscriptionBundle getSubscriptionBundleFromKey(String bundleKey) {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public SubscriptionBundle getSubscriptionBundleFromId(UUID bundleId) {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public SubscriptionBundle createSubscriptionBundle(
+				SubscriptionBundleData bundle) {
+			throw new UnsupportedOperationException();
+
+		}
+
+		@Override
+		public Subscription getSubscriptionFromId(UUID subscriptionId) {
+			return new BrainDeadSubscription();
+
+		}
+
+		@Override
+		public UUID getAccountIdFromSubscriptionId(UUID subscriptionId) {
+			throw new UnsupportedOperationException();
+
+		}
 
+		@Override
+		public Subscription getBaseSubscription(UUID bundleId) {
+			throw new UnsupportedOperationException();
+
+		}
+
+		@Override
+		public List<Subscription> getSubscriptions(UUID bundleId) {
+			throw new UnsupportedOperationException();
+
+		}
+
+		@Override
+		public List<Subscription> getSubscriptionsForKey(String bundleKey) {
+			throw new UnsupportedOperationException();
+
+		}
+
+		@Override
+		public void updateSubscription(SubscriptionData subscription) {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public void createNextPhaseEvent(UUID subscriptionId,
+				EntitlementEvent nextPhase) {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public EntitlementEvent getEventById(UUID eventId) {
+			throw new UnsupportedOperationException();
+
+		}
+
+		@Override
+		public List<EntitlementEvent> getEventsForSubscription(
+				UUID subscriptionId) {
+			throw new UnsupportedOperationException();
+
+		}
+
+		@Override
+		public List<EntitlementEvent> getPendingEventsForSubscription(
+				UUID subscriptionId) {
+			throw new UnsupportedOperationException();
+
+		}
+
+		@Override
+		public void createSubscription(SubscriptionData subscription,
+				List<EntitlementEvent> initialEvents) {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public void cancelSubscription(UUID subscriptionId,
+				EntitlementEvent cancelEvent) {
+			throw new UnsupportedOperationException();
+
+		}
+
+		@Override
+		public void uncancelSubscription(UUID subscriptionId,
+				List<EntitlementEvent> uncancelEvents) {
+			throw new UnsupportedOperationException();
+	
+		}
+
+		@Override
+		public void changePlan(UUID subscriptionId,
+				List<EntitlementEvent> changeEvents) {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public void migrate(UUID acountId, AccountMigrationData data) {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public void undoMigration(UUID accountId) {
+			throw new UnsupportedOperationException();
+		}
+		
+	}
+	
 	@BeforeClass(groups={"setup"})
 	public void setup() throws ServiceException, IOException, ClassNotFoundException, SQLException {
 		//TestApiBase.loadSystemPropertiesFromClasspath("/entitlement.properties");
@@ -91,7 +252,8 @@ public class TestNextBillingDateNotifier {
         dao = dbi.onDemand(DummySqlTest.class);
         eventBus = g.getInstance(Bus.class);
         helper = g.getInstance(MysqlTestingHelper.class);
-        notifier = new DefaultNextBillingDateNotifier(g.getInstance(NotificationQueueService.class), eventBus, g.getInstance(InvoiceConfig.class), g.getInstance(EntitlementDao.class));
+        notificationQueueService = g.getInstance(NotificationQueueService.class);
+        notifier = new DefaultNextBillingDateNotifier(notificationQueueService,g.getInstance(InvoiceConfig.class), new MockEntitlementDao(), listener);
         startMysql();
 	}
 
@@ -105,48 +267,30 @@ public class TestNextBillingDateNotifier {
         helper.initDb(entitlementDdl);
 	}
 
-	public static class NextBillingEventListener {
-		private int eventCount=0;
-		private NextBillingDateEvent event;
 
-		public int getEventCount() {
-			return eventCount;
-		}
-
-		@Subscribe
-		public synchronized void processEvent(NextBillingDateEvent event) {
-			eventCount++;
-			this.event = event;
-			//log.debug("Got event {} {}", event.name, event.value);
-		}
-		
-		public NextBillingDateEvent getLatestEvent() {
-			return event;
-		}
-	}
-
-	@Test(enabled=false, groups="slow")
+	@Test(enabled=true, groups="slow")
 	public void test() throws Exception {
-		final UUID subscriptionId = new UUID(0L,1000L);
+		final UUID subscriptionId = new UUID(0L,1L);
 		final DateTime now = new DateTime();
 		final DateTime readyTime = now.plusMillis(2000);
+		final NextBillingDatePoster poster = new DefaultNextBillingDatePoster(notificationQueueService); 
 
-		final NextBillingEventListener listener = new NextBillingEventListener();
 		eventBus.start();
 		notifier.initialize();
 		notifier.start();
+		
 
-        eventBus.register(listener);
 		dao.inTransaction(new Transaction<Void, DummySqlTest>() {
 			@Override
 			public Void inTransaction(DummySqlTest transactional,
 					TransactionStatus status) throws Exception {
 
-				notifier.insertNextBillingNotification(transactional, subscriptionId, readyTime);
+				poster.insertNextBillingNotification(transactional, subscriptionId, readyTime);
 				return null;
 			}
 		});
-
+		
+		
 		// Move time in the future after the notification effectiveDate
 		((ClockMock) clock).setDeltaFromReality(3000);
 
@@ -159,6 +303,6 @@ public class TestNextBillingDateNotifier {
 	        });
 
 		Assert.assertEquals(listener.getEventCount(), 1);
-		Assert.assertEquals(listener.getLatestEvent().getSubscriptionId(), subscriptionId);
+		Assert.assertEquals(listener.getLatestSubscriptionId(), subscriptionId);
 	}
 }
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..f0de1ce 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,31 +33,38 @@ 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.InvoiceApiException;
 import com.ning.billing.invoice.api.InvoiceItem;
 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.InvoiceGenerator;
 import com.ning.billing.invoice.model.InvoiceItemList;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.DefaultClock;
+
 import org.joda.time.DateTime;
 import org.testng.annotations.Test;
 
 import javax.annotation.Nullable;
 import java.math.BigDecimal;
+import java.util.List;
 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();
+
+
+    private final InvoiceGenerator generator = new DefaultInvoiceGenerator(new DefaultClock());
 
     @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 +72,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 +83,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()));
@@ -86,24 +92,24 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         Plan plan = new MockPlan();
         BigDecimal rate1 = TEN;
         PlanPhase phase = createMockMonthlyPlanPhase(rate1);
-        
+
         BillingEvent event = createBillingEvent(sub.getId(), startDate, plan, phase, 1);
         events.add(event);
 
         InvoiceItemList existingInvoiceItems = new InvoiceItemList();
-        
+
         DateTime targetDate = buildDateTime(2011, 10, 3);
         UUID accountId = UUID.randomUUID();
         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()));
@@ -116,13 +122,13 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         events.add(event);
 
         InvoiceItemList existingInvoiceItems = new InvoiceItemList();
-        
-        DateTime targetDate = buildDateTime(2011, 10, 3);        
+
+        DateTime targetDate = buildDateTime(2011, 10, 3);
         UUID accountId = UUID.randomUUID();
         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 +137,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
     }
 
     @Test
-    public void testTwoMonthlySubscriptionsWithAlignedBillingDates() {
+    public void testTwoMonthlySubscriptionsWithAlignedBillingDates() throws InvoiceApiException {
         BillingEventSet events = new BillingEventSet();
 
         Plan plan1 = new MockPlan();
@@ -141,7 +147,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         Plan plan2 = new MockPlan();
         BigDecimal rate2 = TEN;
         PlanPhase phase2 = createMockMonthlyPlanPhase(rate2);
-        
+
         Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
 
         BillingEvent event1 = createBillingEvent(sub.getId(), buildDateTime(2011, 9, 1), plan1, phase1, 1);
@@ -161,14 +167,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 +189,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 +205,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 +232,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()));
@@ -241,7 +246,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         Plan plan1 = new MockPlan();
         BigDecimal rate = FIVE;
         PlanPhase phase1 = createMockMonthlyPlanPhase(rate);
-        
+
         BillingEvent event1 = createBillingEvent(sub.getId(), startDate, plan1, phase1, 1);
         events.add(event1);
 
@@ -258,7 +263,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,94 +424,35 @@ 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();
         DateTime targetDate = buildDateTime(2011, 1, 1);
         events.add(createBillingEvent(UUID.randomUUID(), targetDate, plan, planPhase, 1));
 
-        InvoiceGenerator invoiceGenerator = new DefaultInvoiceGenerator();
-        Invoice invoice = invoiceGenerator.generateInvoice(UUID.randomUUID(), events, null, targetDate, Currency.USD);
+        Invoice invoice = generator.generateInvoice(UUID.randomUUID(), events, null, targetDate, Currency.USD);
 
         assertEquals(invoice.getNumberOfItems(), 1);
     }
 
     @Test
-    public void testEndDateIsCorrect() {
+    public void testEndDateIsCorrect() throws InvoiceApiException {
         Plan plan = new MockPlan();
         PlanPhase planPhase = createMockMonthlyPlanPhase(ZERO);
         BillingEventSet events = new BillingEventSet();
         DateTime targetDate = new DateTime();
         events.add(createBillingEvent(UUID.randomUUID(), targetDate, plan, planPhase, targetDate.getDayOfMonth()));
 
-        InvoiceGenerator invoiceGenerator = new DefaultInvoiceGenerator();
-        Invoice invoice = invoiceGenerator.generateInvoice(UUID.randomUUID(), events, null, targetDate, Currency.USD);
-        InvoiceItem item = invoice.getInvoiceItems().get(0);
+        Invoice invoice = generator.generateInvoice(UUID.randomUUID(), events, null, targetDate, Currency.USD);
+        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,9 +483,85 @@ 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.getStartDate().compareTo(changeDate), 0);
    }
 
+    @Test
+    public void testMixedModeLifeCycle() throws InvoiceApiException {
+        // create a subscription with a fixed price and recurring price
+        Plan plan1 = new MockPlan();
+        BigDecimal monthlyRate = FIVE;
+        BigDecimal fixedCost = TEN;
+        PlanPhase phase1 = createMockMonthlyPlanPhase(monthlyRate, fixedCost, PhaseType.TRIAL);
+
+        BillingEventSet events = new BillingEventSet();
+        UUID subscriptionId = UUID.randomUUID();
+        UUID accountId = UUID.randomUUID();
+
+        DateTime startDate = new DateTime(2011, 1, 1, 3, 40, 27, 0);
+        BillingEvent event1 = createBillingEvent(subscriptionId, startDate, plan1, phase1, 1);
+        events.add(event1);
+
+        // ensure both components are invoiced
+        Invoice invoice1 = generator.generateInvoice(accountId, events, null, startDate, Currency.USD);
+        assertNotNull(invoice1);
+        assertEquals(invoice1.getNumberOfItems(), 2);
+        assertEquals(invoice1.getTotalAmount(), FIFTEEN);
+
+        List<InvoiceItem> existingItems = invoice1.getInvoiceItems();
+
+        // move forward in time one billing period
+        DateTime currentDate = startDate.plusMonths(1);
+
+        // ensure that only the recurring price is invoiced
+        Invoice invoice2 = generator.generateInvoice(accountId, events, existingItems, currentDate, Currency.USD);
+        assertNotNull(invoice2);
+        assertEquals(invoice2.getNumberOfItems(), 1);
+        assertEquals(invoice2.getTotalAmount(), FIVE);
+    }
+
+    @Test
+    public void testFixedModePlanChange() throws InvoiceApiException {
+        // create a subscription with a fixed price and recurring price
+        Plan plan1 = new MockPlan();
+        BigDecimal fixedCost1 = TEN;
+        BigDecimal fixedCost2 = TWENTY;
+        PlanPhase phase1 = createMockMonthlyPlanPhase(null, fixedCost1, PhaseType.TRIAL);
+        PlanPhase phase2 = createMockMonthlyPlanPhase(null, fixedCost2, PhaseType.EVERGREEN);
+
+        BillingEventSet events = new BillingEventSet();
+        UUID subscriptionId = UUID.randomUUID();
+        UUID accountId = UUID.randomUUID();
+
+        DateTime startDate = new DateTime(2011, 1, 1, 3, 40, 27, 0);
+        BillingEvent event1 = createBillingEvent(subscriptionId, startDate, plan1, phase1, 1);
+        events.add(event1);
+
+        // ensure that a single invoice item is generated for the fixed cost
+        Invoice invoice1 = generator.generateInvoice(accountId, events, null, startDate, Currency.USD);
+        assertNotNull(invoice1);
+        assertEquals(invoice1.getNumberOfItems(), 1);
+        assertEquals(invoice1.getTotalAmount(), fixedCost1);
+
+        List<InvoiceItem> existingItems = invoice1.getInvoiceItems();
+
+        // move forward in time one billing period
+        DateTime phaseChangeDate = startDate.plusMonths(1);
+        BillingEvent event2 = createBillingEvent(subscriptionId, phaseChangeDate, plan1, phase2, 1);
+        events.add(event2);
+
+        // ensure that a single invoice item is generated for the fixed cost
+        Invoice invoice2 = generator.generateInvoice(accountId, events, existingItems, phaseChangeDate, Currency.USD);
+        assertNotNull(invoice2);
+        assertEquals(invoice2.getNumberOfItems(), 1);
+        assertEquals(invoice2.getTotalAmount(), fixedCost2);
+    }
+
     private MockPlanPhase createMockMonthlyPlanPhase() {
         return new MockPlanPhase(null, null, BillingPeriod.MONTHLY);
     }
@@ -549,18 +571,19 @@ 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);
     }
 
+    private MockPlanPhase createMockMonthlyPlanPhase(@Nullable BigDecimal recurringRate, final BigDecimal fixedCost,
+                                                     final PhaseType phaseType) {
+        MockInternationalPrice recurringPrice = (recurringRate == null) ? null : new MockInternationalPrice(new DefaultPrice(recurringRate, Currency.USD));
+        MockInternationalPrice fixedPrice = (fixedCost == null) ? null : new MockInternationalPrice(new DefaultPrice(fixedCost, Currency.USD));
+
+        return new MockPlanPhase(recurringPrice, fixedPrice, BillingPeriod.MONTHLY, phaseType);
+    }
+
     private MockPlanPhase createMockAnnualPlanPhase(final BigDecimal recurringRate, final PhaseType phaseType) {
         return new MockPlanPhase(new MockInternationalPrice(new DefaultPrice(recurringRate, Currency.USD)),
                                  null, BillingPeriod.ANNUAL, phaseType);
@@ -578,7 +601,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 +611,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
+}
\ 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/InternationalPriceMock.java b/invoice/src/test/java/com/ning/billing/invoice/tests/InternationalPriceMock.java
index 8577544..ab11024 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/InternationalPriceMock.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/InternationalPriceMock.java
@@ -43,4 +43,9 @@ public class InternationalPriceMock implements InternationalPrice {
         return rate;
     }
 
+	@Override
+	public boolean isZero() {
+		return rate.compareTo(BigDecimal.ZERO) == 0;
+	}
+
 }
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..c90bf69 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java
@@ -24,7 +24,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/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
index 23ff871..c2e4883 100644
--- a/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
+++ b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
@@ -208,7 +208,6 @@ public class DefaultPaymentApi implements PaymentApi {
                 }
             }
 
-
             if (paymentInfo.getPaymentId() != null) {
                 paymentDao.updatePaymentAttemptWithPaymentId(paymentAttempt.getPaymentAttemptId(), paymentInfo.getPaymentId());
             }
@@ -217,9 +216,9 @@ public class DefaultPaymentApi implements PaymentApi {
         invoicePaymentApi.notifyOfPaymentAttempt(new DefaultInvoicePayment(paymentAttempt.getPaymentAttemptId(),
                                                                            invoice.getId(),
                                                                            paymentAttempt.getPaymentAttemptDate(),
-                                                                           paymentInfo == null ? null : paymentInfo.getAmount(),
-//                                                                                 paymentInfo.getRefundAmount(), TODO
-                                                                           paymentInfo == null ? null : invoice.getCurrency()));
+                                                                           paymentInfo == null || paymentInfo.getStatus().equalsIgnoreCase("Error") ? null : paymentInfo.getAmount(),
+//                                                                         paymentInfo.getRefundAmount(), TODO
+                                                                           paymentInfo == null || paymentInfo.getStatus().equalsIgnoreCase("Error") ? null : invoice.getCurrency()));
 
         return paymentOrError;
     }
@@ -262,7 +261,8 @@ public class DefaultPaymentApi implements PaymentApi {
     }
 
     @Override
-    public Either<PaymentError, Void> updatePaymentProviderAccountContact(Account account) {
+    public Either<PaymentError, Void> updatePaymentProviderAccountContact(String externalKey) {
+    	Account account = accountUserApi.getAccountByKey(externalKey);
         final PaymentProviderPlugin plugin = getPaymentProviderPlugin(account);
         return plugin.updatePaymentProviderAccountExistingContact(account);
     }
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPlugin.java b/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPlugin.java
new file mode 100644
index 0000000..71c7e41
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPlugin.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.provider;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.payment.api.Either;
+import com.ning.billing.payment.api.PaymentError;
+import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentMethodInfo;
+import com.ning.billing.payment.api.PaymentProviderAccount;
+
+public class NoOpPaymentProviderPlugin implements PaymentProviderPlugin {
+
+    @Override
+    public Either<PaymentError, PaymentInfo> processInvoice(Account account, Invoice invoice) {
+        PaymentInfo payment = new PaymentInfo.Builder()
+                                             .setPaymentId(UUID.randomUUID().toString())
+                                             .setAmount(invoice.getBalance())
+                                             .setStatus("Processed")
+                                             .setCreatedDate(new DateTime(DateTimeZone.UTC))
+                                             .setEffectiveDate(new DateTime(DateTimeZone.UTC))
+                                             .setType("Electronic")
+                                             .build();
+        return Either.right(payment);
+    }
+
+    @Override
+    public Either<PaymentError, PaymentInfo> getPaymentInfo(String paymentId) {
+        return Either.right(null);
+    }
+
+    @Override
+    public Either<PaymentError, String> createPaymentProviderAccount(Account account) {
+        return Either.left(new PaymentError("unsupported", "Account creation not supported in this plugin"));
+    }
+
+    @Override
+    public Either<PaymentError, PaymentProviderAccount> getPaymentProviderAccount(String accountKey) {
+        return Either.right(null);
+    }
+
+    @Override
+    public Either<PaymentError, String> addPaymentMethod(String accountKey, PaymentMethodInfo paymentMethod) {
+        return Either.right(null);
+    }
+
+    public void setDefaultPaymentMethodOnAccount(PaymentProviderAccount account, String paymentMethodId) {
+        // NO-OP
+    }
+
+    @Override
+    public Either<PaymentError, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethod) {
+        return Either.right(paymentMethod);
+    }
+
+    @Override
+    public Either<PaymentError, Void> deletePaymentMethod(String accountKey, String paymentMethodId) {
+        return Either.right(null);
+    }
+
+    @Override
+    public Either<PaymentError, PaymentMethodInfo> getPaymentMethodInfo(String paymentMethodId) {
+        return Either.right(null);
+    }
+
+    @Override
+    public Either<PaymentError, List<PaymentMethodInfo>> getPaymentMethods(final String accountKey) {
+        return Either.right(Arrays.<PaymentMethodInfo>asList());
+    }
+
+    @Override
+    public Either<PaymentError, Void> updatePaymentGateway(String accountKey) {
+        return Either.right(null);
+    }
+
+    @Override
+    public Either<PaymentError, Void> updatePaymentProviderAccountExistingContact(Account account) {
+        return Either.right(null);
+    }
+
+    @Override
+    public Either<PaymentError, Void> updatePaymentProviderAccountWithNewContact(Account account) {
+        return Either.right(null);
+    }
+
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPluginModule.java b/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPluginModule.java
new file mode 100644
index 0000000..a403274
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPluginModule.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.provider;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.name.Names;
+
+public class NoOpPaymentProviderPluginModule extends AbstractModule {
+    private final String instanceName;
+
+    public NoOpPaymentProviderPluginModule(String instanceName) {
+        this.instanceName = instanceName;
+    }
+
+    @Override
+    protected void configure() {
+        bind(NoOpPaymentProviderPlugin.class)
+            .annotatedWith(Names.named(instanceName))
+            .toProvider(new NoOpPaymentProviderPluginProvider(instanceName))
+            .asEagerSingleton();
+    }
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPluginProvider.java b/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPluginProvider.java
new file mode 100644
index 0000000..5ba98b7
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPluginProvider.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.provider;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class NoOpPaymentProviderPluginProvider implements Provider<NoOpPaymentProviderPlugin> {
+    private PaymentProviderPluginRegistry registry;
+    private final String instanceName;
+
+    public NoOpPaymentProviderPluginProvider(String instanceName) {
+        this.instanceName = instanceName;
+    }
+
+    @Inject
+    public void setPaymentProviderPluginRegistry(PaymentProviderPluginRegistry registry) {
+        this.registry = registry;
+    }
+
+    @Override
+    public NoOpPaymentProviderPlugin get() {
+        NoOpPaymentProviderPlugin plugin = new NoOpPaymentProviderPlugin();
+
+        registry.register(plugin, instanceName);
+        return plugin;
+    }
+
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java b/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java
index 2c86e3b..d53c236 100644
--- a/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java
+++ b/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java
@@ -64,11 +64,7 @@ public class RequestProcessor {
             }
             else {
                 List<Either<PaymentError, PaymentInfo>> results = paymentApi.createPayment(account, Arrays.asList(event.getInvoiceId().toString()));
-
-                if (results.isEmpty()) {
-                    eventBus.post(new PaymentError("unknown", "No payment processed"));
-                }
-                else {
+                if (!results.isEmpty()) {
                     Either<PaymentError, PaymentInfo> result = results.get(0);
                     eventBus.post(result.isLeft() ? result.getLeft() : result.getRight());
                 }
diff --git a/payment/src/main/java/com/ning/billing/payment/RetryService.java b/payment/src/main/java/com/ning/billing/payment/RetryService.java
index 31020cc..027ee28 100644
--- a/payment/src/main/java/com/ning/billing/payment/RetryService.java
+++ b/payment/src/main/java/com/ning/billing/payment/RetryService.java
@@ -61,7 +61,7 @@ public class RetryService implements KillbillService {
     public void initialize() throws NotificationQueueAlreadyExists {
         retryQueue = notificationQueueService.createNotificationQueue(SERVICE_NAME, QUEUE_NAME, new NotificationQueueHandler() {
             @Override
-            public void handleReadyNotification(String notificationKey) {
+            public void handleReadyNotification(String notificationKey, DateTime eventDateTime) {
                 retry(notificationKey);
             }
         },
diff --git a/payment/src/main/java/com/ning/billing/payment/setup/PaymentConfig.java b/payment/src/main/java/com/ning/billing/payment/setup/PaymentConfig.java
index 6ec83c4..46f00fc 100644
--- a/payment/src/main/java/com/ning/billing/payment/setup/PaymentConfig.java
+++ b/payment/src/main/java/com/ning/billing/payment/setup/PaymentConfig.java
@@ -20,14 +20,12 @@ import java.util.List;
 
 import org.skife.config.Config;
 import org.skife.config.Default;
-import org.skife.config.DefaultNull;
-import org.skife.config.TimeSpan;
 
 import com.ning.billing.util.notificationq.NotificationConfig;
 
 public interface PaymentConfig extends NotificationConfig {
     @Config("killbill.payment.provider.default")
-    @DefaultNull
+    @Default("noop")
     public String getDefaultPaymentProvider();
 
     @Config("killbill.payment.retry.days")
diff --git a/payment/src/main/java/com/ning/billing/payment/setup/PaymentModule.java b/payment/src/main/java/com/ning/billing/payment/setup/PaymentModule.java
index b8af029..85641ed 100644
--- a/payment/src/main/java/com/ning/billing/payment/setup/PaymentModule.java
+++ b/payment/src/main/java/com/ning/billing/payment/setup/PaymentModule.java
@@ -25,6 +25,7 @@ import com.ning.billing.payment.RequestProcessor;
 import com.ning.billing.payment.RetryService;
 import com.ning.billing.payment.api.DefaultPaymentApi;
 import com.ning.billing.payment.api.PaymentApi;
+import com.ning.billing.payment.api.PaymentService;
 import com.ning.billing.payment.dao.DefaultPaymentDao;
 import com.ning.billing.payment.dao.PaymentDao;
 import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
@@ -60,7 +61,7 @@ public class PaymentModule extends AbstractModule {
         bind(PaymentProviderPluginRegistry.class).asEagerSingleton();
         bind(PaymentApi.class).to(DefaultPaymentApi.class).asEagerSingleton();
         bind(RequestProcessor.class).asEagerSingleton();
-        bind(PaymentService.class).asEagerSingleton();
+        bind(PaymentService.class).to(DefaultPaymentService.class).asEagerSingleton();
         installPaymentProviderPlugins(paymentConfig);
         installPaymentDao();
     }
diff --git a/payment/src/main/resources/com/ning/billing/payment/ddl.sql b/payment/src/main/resources/com/ning/billing/payment/ddl.sql
index a8bd47f..abbd8a6 100644
--- a/payment/src/main/resources/com/ning/billing/payment/ddl.sql
+++ b/payment/src/main/resources/com/ning/billing/payment/ddl.sql
@@ -25,7 +25,7 @@ CREATE TABLE payments (
       status varchar(20) COLLATE utf8_bin,
       reference_id varchar(36) COLLATE utf8_bin,
       payment_type varchar(20) COLLATE utf8_bin,
-      payment_method_id varchar(20) COLLATE utf8_bin,
+      payment_method_id varchar(36) COLLATE utf8_bin,
       payment_method varchar(20) COLLATE utf8_bin,
       card_type varchar(20) COLLATE utf8_bin,
       card_country varchar(50) COLLATE utf8_bin,
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 1f8f681..b9d7586 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
@@ -39,7 +39,7 @@ import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.account.api.user.AccountBuilder;
 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;
@@ -70,14 +70,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()));
@@ -165,7 +164,7 @@ public abstract class TestPaymentApi {
                                                                   .billingCycleDay(account.getBillCycleDay())
                                                                   .build();
 
-        Either<PaymentError, Void> voidOrError = paymentApi.updatePaymentProviderAccountContact(accountToUpdate);
+        Either<PaymentError, Void> voidOrError = paymentApi.updatePaymentProviderAccountContact(accountToUpdate.getExternalKey());
         assertTrue(voidOrError.isRight());
     }
 
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 fd7f5c1..ac05da4 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;
@@ -83,16 +83,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;
@@ -102,7 +104,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);
     }

pom.xml 11(+11 -0)

diff --git a/pom.xml b/pom.xml
index b6f6e78..3a6a739 100644
--- a/pom.xml
+++ b/pom.xml
@@ -84,6 +84,17 @@
             </dependency>
             <dependency>
                 <groupId>com.ning.billing</groupId>
+                <artifactId>killbill-payment</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.ning.billing</groupId>
+                <artifactId>killbill-payment</artifactId>
+                <version>${project.version}</version>
+                <type>test-jar</type>
+            </dependency>
+            <dependency>
+                <groupId>com.ning.billing</groupId>
                 <artifactId>killbill-catalog</artifactId>
                 <version>${project.version}</version>
             </dependency>
diff --git a/util/src/main/java/com/ning/billing/util/bus/InMemoryBus.java b/util/src/main/java/com/ning/billing/util/bus/InMemoryBus.java
index 9e4460b..31105c8 100644
--- a/util/src/main/java/com/ning/billing/util/bus/InMemoryBus.java
+++ b/util/src/main/java/com/ning/billing/util/bus/InMemoryBus.java
@@ -28,9 +28,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
 
 public class InMemoryBus implements Bus {
 
-    // STEPH config ?
-    private final static int MAX_EVENT_THREADS = 1;
-
     private final static String EVENT_BUS_IDENTIFIER = "bus-service";
     private final static String EVENT_BUS_GROUP_NAME = "bus-grp";
     private final static String EVENT_BUS_TH_NAME = "bus-th";
@@ -68,7 +65,7 @@ public class InMemoryBus implements Bus {
     public InMemoryBus() {
 
         final ThreadGroup group = new ThreadGroup(EVENT_BUS_GROUP_NAME);
-        Executor executor = Executors.newFixedThreadPool(MAX_EVENT_THREADS, new ThreadFactory() {
+        Executor executor = Executors.newCachedThreadPool(new ThreadFactory() {
             @Override
             public Thread newThread(Runnable r) {
                 return new Thread(group, r, EVENT_BUS_TH_NAME);
diff --git a/util/src/main/java/com/ning/billing/util/glue/GlobalLockerModule.java b/util/src/main/java/com/ning/billing/util/glue/GlobalLockerModule.java
index 93d31e0..a6e6f66 100644
--- a/util/src/main/java/com/ning/billing/util/glue/GlobalLockerModule.java
+++ b/util/src/main/java/com/ning/billing/util/glue/GlobalLockerModule.java
@@ -17,8 +17,8 @@
 package com.ning.billing.util.glue;
 
 import com.google.inject.AbstractModule;
-import com.ning.billing.util.globalLocker.GlobalLocker;
-import com.ning.billing.util.globalLocker.MySqlGlobalLocker;
+import com.ning.billing.util.globallocker.GlobalLocker;
+import com.ning.billing.util.globallocker.MySqlGlobalLocker;
 
 public class GlobalLockerModule extends AbstractModule {
     @Override
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueue.java b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueue.java
index 3c19a2b..8e2aaf8 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueue.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueue.java
@@ -38,26 +38,29 @@ public class DefaultNotificationQueue extends NotificationQueueBase {
     }
 
     @Override
-    protected void doProcessEvents(final int sequenceId) {
+    protected int doProcessEvents(final int sequenceId) {
 
         logDebug("ENTER doProcessEvents");
         List<Notification> notifications = getReadyNotifications(sequenceId);
         if (notifications.size() == 0) {
             logDebug("EXIT doProcessEvents");
-            return;
+            return 0;
         }
 
         logDebug("START processing %d events at time %s", notifications.size(), clock.getUTCNow().toDate());
 
+        int result = 0;
         for (final Notification cur : notifications) {
             nbProcessedEvents.incrementAndGet();
             logDebug("handling notification %s, key = %s for time %s",
                     cur.getUUID(), cur.getNotificationKey(), cur.getEffectiveDate());
-            handler.handleReadyNotification(cur.getNotificationKey());
+            handler.handleReadyNotification(cur.getNotificationKey(), cur.getEffectiveDate());
+            result++;
             clearNotification(cur);
             logDebug("done handling notification %s, key = %s for time %s",
                     cur.getUUID(), cur.getNotificationKey(), cur.getEffectiveDate());
         }
+        return result;
     }
 
     @Override
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueueService.java b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueueService.java
index 91e7110..3b96ee4 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueueService.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueueService.java
@@ -36,5 +36,4 @@ public class DefaultNotificationQueueService extends NotificationQueueServiceBas
             NotificationConfig config) {
         return new DefaultNotificationQueue(dbi, clock, svcName, queueName, handler, config);
     }
-
 }
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueue.java b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueue.java
index 4ea38f7..e1dcdbf 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueue.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueue.java
@@ -38,8 +38,9 @@ public interface NotificationQueue {
     * This is only valid when the queue has been configured with isNotificationProcessingOff is true
     * In which case, it will callback users for all the ready notifications.
     *
+    * @return the number of entries we processed
     */
-   public void processReadyNotification();
+   public int processReadyNotification();
 
    /**
     * Stops the queue. Blocks until queue is completely stopped.
@@ -55,4 +56,10 @@ public interface NotificationQueue {
     */
    public void startQueue();
 
+   /**
+    *
+    * @return the name of that queue
+    */
+   public String getFullQName();
+
 }
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueBase.java b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueBase.java
index 9a42d2e..cc1ea28 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueBase.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueBase.java
@@ -57,10 +57,10 @@ public abstract class NotificationQueueBase implements NotificationQueue {
 
     // Use this object's monitor for synchronization (no need for volatile)
     protected boolean isProcessingEvents;
-    
+
     private boolean startedComplete = false;
     private boolean stoppedComplete = false;
-    
+
     // Package visibility on purpose
     NotificationQueueBase(final Clock clock,  final String svcName, final String queueName, final NotificationQueueHandler handler, final NotificationConfig config) {
         this.clock = clock;
@@ -88,8 +88,8 @@ public abstract class NotificationQueueBase implements NotificationQueue {
 
 
     @Override
-    public void processReadyNotification() {
-        doProcessEvents(sequenceId.incrementAndGet());
+    public int processReadyNotification() {
+        return doProcessEvents(sequenceId.incrementAndGet());
     }
 
 
@@ -181,14 +181,14 @@ public abstract class NotificationQueueBase implements NotificationQueue {
         });
         waitForNotificationStartCompletion();
     }
-    
+
     private void completedQueueStop() {
     	synchronized (this) {
     		stoppedComplete = true;
             this.notifyAll();
         }
     }
-    
+
     private void completedQueueStart() {
         synchronized (this) {
         	startedComplete = true;
@@ -233,9 +233,10 @@ public abstract class NotificationQueueBase implements NotificationQueue {
         }
     }
 
-    protected String getFullQName() {
+    @Override
+    public String getFullQName() {
         return svcName + ":" +  queueName;
     }
 
-    protected abstract void doProcessEvents(int sequenceId);
+    protected abstract int doProcessEvents(int sequenceId);
 }
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueService.java b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueService.java
index 4fb17b5..4d56b03 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueService.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueService.java
@@ -16,6 +16,8 @@
 
 package com.ning.billing.util.notificationq;
 
+import org.joda.time.DateTime;
+
 
 public interface NotificationQueueService {
 
@@ -25,7 +27,7 @@ public interface NotificationQueueService {
          *
          * @param notificationKey the notification key associated to that notification entry
          */
-        public void handleReadyNotification(String notificationKey);
+        public void handleReadyNotification(String notificationKey, DateTime eventDateTime);
      }
 
     public static final class NotificationQueueAlreadyExists extends Exception {
@@ -57,7 +59,7 @@ public interface NotificationQueueService {
      * @throws com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueAlreadyExists is the queue associated with that service and name already exits
      *
      */
-    NotificationQueue createNotificationQueue(final String svcName, final String queueName, final NotificationQueueHandler handler, final NotificationConfig config)
+    public NotificationQueue createNotificationQueue(final String svcName, final String queueName, final NotificationQueueHandler handler, final NotificationConfig config)
         throws NotificationQueueAlreadyExists;
 
     /**
@@ -69,7 +71,14 @@ public interface NotificationQueueService {
      *
      * @throws NoSuchNotificationQueue if queue does not exist
      */
-    NotificationQueue getNotificationQueue(final String svcName, final String queueName)
+    public NotificationQueue getNotificationQueue(final String svcName, final String queueName)
         throws NoSuchNotificationQueue;
 
+
+    /**
+     *
+     * @param services
+     * @return the number of processed notifications
+     */
+    public int triggerManualQueueProcessing(final String [] services, final Boolean keepRunning);
 }
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueServiceBase.java b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueServiceBase.java
index 98a10b1..3f8f26f 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueServiceBase.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueServiceBase.java
@@ -16,12 +16,16 @@
 
 package com.ning.billing.util.notificationq;
 
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Joiner;
 import com.google.inject.Inject;
 import com.ning.billing.util.clock.Clock;
 
@@ -79,6 +83,48 @@ public abstract class NotificationQueueServiceBase implements NotificationQueueS
     }
 
 
+    //
+    // Test ONLY
+    //
+    @Override
+    public int triggerManualQueueProcessing(final String [] services, final Boolean keepRunning) {
+
+        int result = 0;
+
+        List<NotificationQueue> manualQueues = null;
+        if (services == null) {
+            manualQueues = new ArrayList<NotificationQueue>(queues.values());
+        } else {
+            Joiner join = Joiner.on(",");
+            join.join(services);
+
+            log.info("Trigger manual processing for services {} ", join.toString());
+            manualQueues = new LinkedList<NotificationQueue>();
+            synchronized (queues) {
+                for (String svc : services) {
+                    addQueuesForService(manualQueues, svc);
+                }
+            }
+        }
+        for (NotificationQueue cur : manualQueues) {
+            int processedNotifications = 0;
+            do {
+                processedNotifications = cur.processReadyNotification();
+                log.info("Got {} results from queue {}", processedNotifications, cur.getFullQName());
+                result += processedNotifications;
+            } while(keepRunning && processedNotifications > 0);
+        }
+        return result;
+    }
+
+    private final void addQueuesForService(final List<NotificationQueue> result, final String svcName) {
+        for (String cur : queues.keySet()) {
+            if (cur.startsWith(svcName)) {
+                result.add(queues.get(cur));
+            }
+        }
+    }
+
     protected abstract NotificationQueue createNotificationQueueInternal(String svcName,
             String queueName, NotificationQueueHandler handler,
             NotificationConfig config);
diff --git a/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java b/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
index 1e949a2..5ee7e88 100644
--- a/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
+++ b/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
@@ -39,6 +39,9 @@ import com.mysql.management.MysqldResourceI;
  */
 public class MysqlTestingHelper
 {
+
+    public static final String USE_LOCAL_DB_PROP = "com.ning.billing.dbi.test.useLocalDb";
+
     private static final Logger log = LoggerFactory.getLogger(MysqlTestingHelper.class);
 
     private static final String DB_NAME = "test_killbill";
@@ -47,24 +50,38 @@ public class MysqlTestingHelper
 
     private File dbDir;
     private MysqldResource mysqldResource;
-    private int port = 0;
+    private int port;
 
     public MysqlTestingHelper()
     {
-        // New socket on any free port
-        final ServerSocket socket;
-        try {
-            socket = new ServerSocket(0);
-            port = socket.getLocalPort();
-            socket.close();
-        }
-        catch (IOException e) {
-            Assert.fail();
+        if (isUsingLocalInstance()) {
+            port = 3306;
+        } else {
+            // New socket on any free port
+            final ServerSocket socket;
+            try {
+                socket = new ServerSocket(0);
+                port = socket.getLocalPort();
+                socket.close();
+            }
+            catch (IOException e) {
+                Assert.fail();
+            }
         }
     }
 
+
+    public boolean isUsingLocalInstance() {
+        return (System.getProperty(USE_LOCAL_DB_PROP) != null);
+    }
+
     public void startMysql() throws IOException
     {
+
+        if (isUsingLocalInstance()) {
+            return;
+        }
+
         dbDir = File.createTempFile("mysql", "");
         dbDir.delete();
         dbDir.mkdir();
@@ -87,6 +104,12 @@ public class MysqlTestingHelper
 
     public void cleanupTable(final String table)
     {
+
+        if (!isUsingLocalInstance() && (mysqldResource == null || !mysqldResource.isRunning())) {
+            log.error("Asked to cleanup table " + table + " but MySQL is not running!");
+            return;
+        }
+
         if (mysqldResource == null || !mysqldResource.isRunning()) {
             log.error("Asked to cleanup table " + table + " but MySQL is not running!");
             return;
@@ -122,6 +145,9 @@ public class MysqlTestingHelper
 
     public void initDb(final String ddl) throws IOException
     {
+        if (isUsingLocalInstance()) {
+            return;
+        }
         final IDBI dbi = getDBI();
         dbi.withHandle(new HandleCallback<Void>()
         {
diff --git a/util/src/test/java/com/ning/billing/util/clock/ClockMock.java b/util/src/test/java/com/ning/billing/util/clock/ClockMock.java
index 72fd8f4..ad3e5d3 100644
--- a/util/src/test/java/com/ning/billing/util/clock/ClockMock.java
+++ b/util/src/test/java/com/ning/billing/util/clock/ClockMock.java
@@ -38,13 +38,13 @@ public class ClockMock extends DefaultClock {
 
     private long deltaFromRealityMs;
     private List<Duration> deltaFromRealityDuration;
-    private long deltaFromRealitDurationEpsilon;
+    private long deltaFromRealityDurationEpsilon;
     private DeltaType deltaType;
 
     public ClockMock() {
         deltaType = DeltaType.DELTA_NONE;
         deltaFromRealityMs = 0;
-        deltaFromRealitDurationEpsilon = 0;
+        deltaFromRealityDurationEpsilon = 0;
         deltaFromRealityDuration = null;
     }
 
@@ -58,7 +58,7 @@ public class ClockMock extends DefaultClock {
         return getNow(DateTimeZone.UTC);
     }
 
-    private void logClockAdjustement(DateTime prev, DateTime next) {
+    private void logClockAdjustment(DateTime prev, DateTime next) {
         log.info(String.format("            ************      ADJUSTING CLOCK FROM %s to %s     ********************", prev, next));
     }
 
@@ -67,9 +67,9 @@ public class ClockMock extends DefaultClock {
         deltaType = DeltaType.DELTA_DURATION;
         deltaFromRealityDuration = new ArrayList<Duration>();
         deltaFromRealityDuration.add(delta);
-        deltaFromRealitDurationEpsilon = epsilon;
+        deltaFromRealityDurationEpsilon = epsilon;
         deltaFromRealityMs = 0;
-        logClockAdjustement(prev, getUTCNow());
+        logClockAdjustment(prev, getUTCNow());
     }
 
     public synchronized void addDeltaFromReality(Duration delta) {
@@ -78,16 +78,16 @@ public class ClockMock extends DefaultClock {
             throw new RuntimeException("ClockMock should be set with type DELTA_DURATION");
         }
         deltaFromRealityDuration.add(delta);
-        logClockAdjustement(prev, getUTCNow());
+        logClockAdjustment(prev, getUTCNow());
     }
 
     public synchronized void setDeltaFromReality(long delta) {
         DateTime prev = getUTCNow();
         deltaType = DeltaType.DELTA_ABS;
         deltaFromRealityDuration = null;
-        deltaFromRealitDurationEpsilon = 0;
+        deltaFromRealityDurationEpsilon = 0;
         deltaFromRealityMs = delta;
-        logClockAdjustement(prev, getUTCNow());
+        logClockAdjustment(prev, getUTCNow());
     }
 
     public synchronized void addDeltaFromReality(long delta) {
@@ -96,15 +96,15 @@ public class ClockMock extends DefaultClock {
             throw new RuntimeException("ClockMock should be set with type DELTA_ABS");
         }
         deltaFromRealityDuration = null;
-        deltaFromRealitDurationEpsilon = 0;
+        deltaFromRealityDurationEpsilon = 0;
         deltaFromRealityMs += delta;
-        logClockAdjustement(prev, getUTCNow());
+        logClockAdjustment(prev, getUTCNow());
     }
 
     public synchronized void resetDeltaFromReality() {
         deltaType = DeltaType.DELTA_NONE;
         deltaFromRealityDuration = null;
-        deltaFromRealitDurationEpsilon = 0;
+        deltaFromRealityDurationEpsilon = 0;
         deltaFromRealityMs = 0;
     }
 
@@ -125,8 +125,6 @@ public class ClockMock extends DefaultClock {
 
         DateTime result = input;
         for (Duration cur : deltaFromRealityDuration) {
-
-            int length = cur.getNumber();
             switch (cur.getUnit()) {
             case DAYS:
                 result = result.plusDays(cur.getNumber());
@@ -142,11 +140,11 @@ public class ClockMock extends DefaultClock {
 
             case UNLIMITED:
             default:
-                throw new RuntimeException("ClockMock is adjusting an unlimtited time period");
+                throw new RuntimeException("ClockMock is adjusting an unlimited time period");
             }
         }
-        if (deltaFromRealitDurationEpsilon != 0) {
-            result = result.plus(deltaFromRealitDurationEpsilon);
+        if (deltaFromRealityDurationEpsilon != 0) {
+            result = result.plus(deltaFromRealityDurationEpsilon);
         }
         return result;
     }
diff --git a/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueue.java b/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueue.java
index e1da366..e96d2cf 100644
--- a/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueue.java
+++ b/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueue.java
@@ -59,7 +59,9 @@ public class MockNotificationQueue extends NotificationQueueBase implements Noti
     }
 
     @Override
-    protected void doProcessEvents(int sequenceId) {
+    protected int doProcessEvents(int sequenceId) {
+
+        int result = 0;
 
         List<Notification> processedNotifications = new ArrayList<Notification>();
         List<Notification> oldNotifications = new ArrayList<Notification>();
@@ -73,8 +75,10 @@ public class MockNotificationQueue extends NotificationQueueBase implements Noti
                     readyNotifications.add(cur);
                 }
             }
+
+            result = readyNotifications.size();
             for (Notification cur : readyNotifications) {
-                handler.handleReadyNotification(cur.getNotificationKey());
+                handler.handleReadyNotification(cur.getNotificationKey(), cur.getEffectiveDate());
                 DefaultNotification processedNotification = new DefaultNotification(-1L, cur.getUUID(), hostname, "MockQueue", clock.getUTCNow().plus(config.getDaoClaimTimeMs()), NotificationLifecycleState.PROCESSED, cur.getNotificationKey(), cur.getEffectiveDate());
                 oldNotifications.add(cur);
                 processedNotifications.add(processedNotification);
@@ -87,6 +91,6 @@ public class MockNotificationQueue extends NotificationQueueBase implements Noti
                 notifications.addAll(processedNotifications);
             }
         }
-
+        return result;
     }
 }
diff --git a/util/src/test/java/com/ning/billing/util/notificationq/TestNotificationQueue.java b/util/src/test/java/com/ning/billing/util/notificationq/TestNotificationQueue.java
index eac2d78..fbcf8f9 100644
--- a/util/src/test/java/com/ning/billing/util/notificationq/TestNotificationQueue.java
+++ b/util/src/test/java/com/ning/billing/util/notificationq/TestNotificationQueue.java
@@ -48,7 +48,6 @@ import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
 import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
-import com.google.inject.name.Named;
 import com.google.inject.name.Names;
 import com.ning.billing.dbi.MysqlTestingHelper;
 import com.ning.billing.util.clock.Clock;
@@ -119,7 +118,7 @@ public class TestNotificationQueue {
 		final DefaultNotificationQueue queue = new DefaultNotificationQueue(dbi, clock, "test-svc", "foo",
 				new NotificationQueueHandler() {
 			@Override
-			public void handleReadyNotification(String notificationKey) {
+			public void handleReadyNotification(String notificationKey, DateTime eventDateTime) {
 				synchronized (expectedNotifications) {
 	            	log.info("Handler received key: " + notificationKey);
 
@@ -182,7 +181,7 @@ public class TestNotificationQueue {
 		final DefaultNotificationQueue queue = new DefaultNotificationQueue(dbi, clock, "test-svc", "many",
 				new NotificationQueueHandler() {
 			@Override
-			public void handleReadyNotification(String notificationKey) {
+			public void handleReadyNotification(String notificationKey, DateTime eventDateTime) {
 				synchronized (expectedNotifications) {
 					expectedNotifications.put(notificationKey, Boolean.TRUE);
 					expectedNotifications.notify();
@@ -292,7 +291,7 @@ public class TestNotificationQueue {
 
 		final NotificationQueue queueFred = notificationQueueService.createNotificationQueue("UtilTest", "Fred", new NotificationQueueHandler() {
                 @Override
-                public void handleReadyNotification(String notificationKey) {
+                public void handleReadyNotification(String notificationKey, DateTime eventDateTime)  {
                 	log.info("Fred received key: " + notificationKey);
                 	expectedNotificationsFred.put(notificationKey, Boolean.TRUE);
                 	eventsReceived++;
@@ -302,7 +301,7 @@ public class TestNotificationQueue {
 
 		final NotificationQueue queueBarney = notificationQueueService.createNotificationQueue("UtilTest", "Barney", new NotificationQueueHandler() {
             @Override
-            public void handleReadyNotification(String notificationKey) {
+            public void handleReadyNotification(String notificationKey, DateTime eventDateTime) {
              	log.info("Barney received key: " + notificationKey);
             	expectedNotificationsBarney.put(notificationKey, Boolean.TRUE);
             	eventsReceived++;