killbill-memoizeit

Merge from master

2/14/2012 3:25:26 AM

Changes

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

api/pom.xml 2(+1 -1)

beatrix/pom.xml 14(+12 -2)

catalog/pom.xml 2(+1 -1)

invoice/pom.xml 17(+15 -2)

payment/pom.xml 2(+1 -1)

pom.xml 6(+3 -3)

util/pom.xml 7(+6 -1)

Details

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

diff --git a/account/pom.xml b/account/pom.xml
index 0146205..141e6cb 100644
--- a/account/pom.xml
+++ b/account/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.3-SNAPSHOT</version>
+        <version>0.1.5-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-account</artifactId>
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 fd97eec..8e5c0b3 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,17 @@ 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";
 
diff --git a/account/src/main/java/com/ning/billing/account/dao/DefaultAccountDao.java b/account/src/main/java/com/ning/billing/account/dao/DefaultAccountDao.java
index f501267..1e00dba 100644
--- a/account/src/main/java/com/ning/billing/account/dao/DefaultAccountDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/DefaultAccountDao.java
@@ -22,7 +22,6 @@ import java.util.UUID;
 import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.TransactionStatus;
-
 import com.google.inject.Inject;
 import com.ning.billing.ErrorCode;
 import com.ning.billing.account.api.Account;
@@ -33,23 +32,23 @@ import com.ning.billing.account.api.user.DefaultAccountChangeNotification;
 import com.ning.billing.account.api.user.DefaultAccountCreationEvent;
 import com.ning.billing.util.customfield.CustomField;
 import com.ning.billing.util.customfield.dao.FieldStoreDao;
-import com.ning.billing.util.eventbus.EventBus;
+import com.ning.billing.util.bus.Bus;
 import com.ning.billing.util.tag.Tag;
 import com.ning.billing.util.tag.dao.TagStoreSqlDao;
 
 public class DefaultAccountDao implements AccountDao {
-    private final AccountSqlDao accountDao;
-    private final EventBus eventBus;
+    private final AccountSqlDao accountSqlDao;
+    private final Bus eventBus;
 
     @Inject
-    public DefaultAccountDao(final IDBI dbi, final EventBus eventBus) {
+    public DefaultAccountDao(IDBI dbi, Bus eventBus) {
         this.eventBus = eventBus;
-        this.accountDao = dbi.onDemand(AccountSqlDao.class);
+        this.accountSqlDao = dbi.onDemand(AccountSqlDao.class);
     }
 
     @Override
     public Account getAccountByKey(final String key) {
-        return accountDao.inTransaction(new Transaction<Account, AccountSqlDao>() {
+        return accountSqlDao.inTransaction(new Transaction<Account, AccountSqlDao>() {
             @Override
             public Account inTransaction(final AccountSqlDao accountSqlDao, final TransactionStatus status) throws Exception {
                 Account account = accountSqlDao.getAccountByKey(key);
@@ -67,46 +66,41 @@ public class DefaultAccountDao implements AccountDao {
         if (externalKey == null) {
             throw new AccountApiException(ErrorCode.ACCOUNT_CANNOT_MAP_NULL_KEY, "");
         }
-        return accountDao.getIdFromKey(externalKey);
+        return accountSqlDao.getIdFromKey(externalKey);
     }
 
     @Override
     public Account getById(final String id) {
-        return accountDao.inTransaction(new Transaction<Account, AccountSqlDao>() {
-            @Override
-            public Account inTransaction(final AccountSqlDao accountSqlDao, final TransactionStatus status) throws Exception {
-                Account account = accountSqlDao.getById(id);
-                if (account != null) {
-                    setCustomFieldsFromWithinTransaction(account, accountSqlDao);
-                    setTagsFromWithinTransaction(account, accountSqlDao);
-                }
-                return account;
-            }
-        });
+        Account account = accountSqlDao.getById(id);
+        if (account != null) {
+            setCustomFieldsFromWithinTransaction(account, accountSqlDao);
+            setTagsFromWithinTransaction(account, accountSqlDao);
+        }
+        return account;
     }
 
 
     @Override
     public List<Account> get() {
-        return accountDao.get();
+        return accountSqlDao.get();
     }
 
     @Override
     public void create(final Account account) throws AccountApiException {
         final String key = account.getExternalKey();
         try {
-            accountDao.inTransaction(new Transaction<Void, AccountSqlDao>() {
+
+            accountSqlDao.inTransaction(new Transaction<Void, AccountSqlDao>() {
                 @Override
-                public Void inTransaction(final AccountSqlDao accountSqlDao, final TransactionStatus status) throws AccountApiException, EventBus.EventBusException {
-                    Account currentAccount = accountSqlDao.getAccountByKey(key);
+                public Void inTransaction(final AccountSqlDao transactionalDao, final TransactionStatus status) throws AccountApiException, Bus.EventBusException {
+                    Account currentAccount = transactionalDao.getAccountByKey(key);
                     if (currentAccount != null) {
                         throw new AccountApiException(ErrorCode.ACCOUNT_ALREADY_EXISTS, key);
                     }
-                    accountSqlDao.create(account);
-
-                    saveTagsFromWithinTransaction(account, accountSqlDao, true);
-                    saveCustomFieldsFromWithinTransaction(account, accountSqlDao, true);
+                    transactionalDao.create(account);
 
+                    saveTagsFromWithinTransaction(account, transactionalDao, true);
+                    saveCustomFieldsFromWithinTransaction(account, transactionalDao, true);
                     AccountCreationNotification creationEvent = new DefaultAccountCreationEvent(account);
                     eventBus.post(creationEvent);
                     return null;
@@ -124,9 +118,9 @@ public class DefaultAccountDao implements AccountDao {
     @Override
     public void update(final Account account) throws AccountApiException {
         try {
-            accountDao.inTransaction(new Transaction<Void, AccountSqlDao>() {
+            accountSqlDao.inTransaction(new Transaction<Void, AccountSqlDao>() {
                 @Override
-                public Void inTransaction(final AccountSqlDao accountSqlDao, final TransactionStatus status) throws AccountApiException, EventBus.EventBusException {
+                public Void inTransaction(final AccountSqlDao accountSqlDao, final TransactionStatus status) throws AccountApiException, Bus.EventBusException {
                     String accountId = account.getId().toString();
                     Account currentAccount = accountSqlDao.getById(accountId);
                     if (currentAccount == null) {
@@ -162,9 +156,9 @@ public class DefaultAccountDao implements AccountDao {
     @Override
 	public void deleteByKey(final String externalKey) throws AccountApiException {
     	try {
-            accountDao.inTransaction(new Transaction<Void, AccountSqlDao>() {
+            accountSqlDao.inTransaction(new Transaction<Void, AccountSqlDao>() {
                 @Override
-                public Void inTransaction(final AccountSqlDao accountSqlDao, final TransactionStatus status) throws AccountApiException, EventBus.EventBusException {
+                public Void inTransaction(final AccountSqlDao accountSqlDao, final TransactionStatus status) throws AccountApiException, Bus.EventBusException {
 
                     accountSqlDao.deleteByKey(externalKey);
 
@@ -182,7 +176,7 @@ public class DefaultAccountDao implements AccountDao {
 
     @Override
     public void test() {
-        accountDao.test();
+        accountSqlDao.test();
     }
 
     private void setCustomFieldsFromWithinTransaction(final Account account, final AccountSqlDao transactionalDao) {
@@ -216,7 +210,7 @@ public class DefaultAccountDao implements AccountDao {
 
         List<Tag> tagList = account.getTagList();
         if (tagList != null) {
-            tagStoreDao.save(accountId, objectType, tagList);
+            tagStoreDao.batchSaveFromTransaction(accountId, objectType, tagList);
         }
     }
 
@@ -231,7 +225,7 @@ public class DefaultAccountDao implements AccountDao {
 
         List<CustomField> fieldList = account.getFieldList();
         if (fieldList != null) {
-            fieldStoreDao.save(accountId, objectType, fieldList);
+            fieldStoreDao.batchSaveFromTransaction(accountId, objectType, fieldList);
         }
     }
 
diff --git a/account/src/main/java/com/ning/billing/account/glue/AccountModule.java b/account/src/main/java/com/ning/billing/account/glue/AccountModule.java
index 23087e9..f6e51f4 100644
--- a/account/src/main/java/com/ning/billing/account/glue/AccountModule.java
+++ b/account/src/main/java/com/ning/billing/account/glue/AccountModule.java
@@ -25,7 +25,6 @@ import com.ning.billing.account.api.DefaultAccountService;
 import com.ning.billing.account.api.user.DefaultAccountUserApi;
 import com.ning.billing.account.dao.AccountDao;
 import com.ning.billing.account.dao.DefaultAccountDao;
-import com.ning.billing.util.glue.ClockModule;
 
 public class AccountModule extends AbstractModule {
 
@@ -46,17 +45,11 @@ public class AccountModule extends AbstractModule {
         bind(AccountService.class).to(DefaultAccountService.class).asEagerSingleton();
     }
 
-    protected void installTestModules() {
-        install(new ClockModule());
-    }
-
-
     @Override
     protected void configure() {
         installConfig();
         installAccountDao();
         installAccountService();
         installAccountUserApi();
-        installTestModules();
     }
 }
diff --git a/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java b/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java
index 0a1c642..f4f530e 100644
--- a/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java
+++ b/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java
@@ -29,8 +29,8 @@ import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
 import com.ning.billing.account.glue.AccountModuleWithEmbeddedDb;
-import com.ning.billing.util.eventbus.DefaultEventBusService;
-import com.ning.billing.util.eventbus.EventBusService;
+import com.ning.billing.util.bus.DefaultBusService;
+import com.ning.billing.util.bus.BusService;
 
 public abstract class AccountDaoTestBase {
     protected AccountModuleWithEmbeddedDb module;
@@ -55,8 +55,8 @@ public abstract class AccountDaoTestBase {
             accountDao = injector.getInstance(AccountDao.class);
             accountDao.test();
 
-            EventBusService busService = injector.getInstance(EventBusService.class);
-            ((DefaultEventBusService) busService).startBus();
+            BusService busService = injector.getInstance(BusService.class);
+            ((DefaultBusService) busService).startBus();
         }
         catch (Throwable t) {
             fail(t.toString());
diff --git a/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java b/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java
index d80a05e..b351709 100644
--- a/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java
+++ b/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java
@@ -28,15 +28,15 @@ import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.account.api.AccountChangeNotification;
 import com.ning.billing.account.api.user.DefaultAccountChangeNotification;
 import com.ning.billing.account.api.user.DefaultAccountCreationEvent;
-import com.ning.billing.util.eventbus.EventBus;
-import com.ning.billing.util.eventbus.EventBus.EventBusException;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
 
 public class MockAccountDao implements AccountDao {
-    private final EventBus eventBus;
+    private final Bus eventBus;
     private final Map<String, Account> accounts = new ConcurrentHashMap<String, Account>();
 
     @Inject
-    public MockAccountDao(EventBus eventBus) {
+    public MockAccountDao(Bus eventBus) {
         this.eventBus = eventBus;
     }
 
diff --git a/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithEmbeddedDb.java b/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithEmbeddedDb.java
index e8850dd..1a894a7 100644
--- a/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithEmbeddedDb.java
+++ b/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithEmbeddedDb.java
@@ -17,7 +17,8 @@
 package com.ning.billing.account.glue;
 
 import com.ning.billing.dbi.MysqlTestingHelper;
-import com.ning.billing.util.glue.EventBusModule;
+import com.ning.billing.util.clock.MockClockModule;
+import com.ning.billing.util.glue.BusModule;
 import org.skife.jdbi.v2.IDBI;
 
 import java.io.IOException;
@@ -41,6 +42,7 @@ public class AccountModuleWithEmbeddedDb extends AccountModule {
     protected void configure() {
         bind(IDBI.class).toInstance(helper.getDBI());
         super.configure();
-        install(new EventBusModule());
+        install(new BusModule());
+        install(new MockClockModule());
     }
 }
diff --git a/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithMocks.java b/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithMocks.java
index 5c7b638..1c65c3c 100644
--- a/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithMocks.java
+++ b/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithMocks.java
@@ -27,8 +27,10 @@ public class AccountModuleWithMocks extends AccountModule {
         bind(AccountDao.class).to(MockAccountDao.class);
     }
 
+
     @Override
-    protected void installTestModules() {
+    protected void configure() {
+        super.configure();
         install(new MockClockModule());
     }
 }
diff --git a/analytics/pom.xml b/analytics/pom.xml
index 4cd7332..18bd687 100644
--- a/analytics/pom.xml
+++ b/analytics/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.3-SNAPSHOT</version>
+        <version>0.1.5-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-analytics</artifactId>
diff --git a/analytics/src/main/java/com/ning/billing/analytics/api/AnalyticsService.java b/analytics/src/main/java/com/ning/billing/analytics/api/AnalyticsService.java
index a08e3ab..4adb21e 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/api/AnalyticsService.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/api/AnalyticsService.java
@@ -19,7 +19,7 @@ package com.ning.billing.analytics.api;
 import com.google.inject.Inject;
 import com.ning.billing.analytics.AnalyticsListener;
 import com.ning.billing.lifecycle.LifecycleHandlerType;
-import com.ning.billing.util.eventbus.EventBus;
+import com.ning.billing.util.bus.Bus;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -30,10 +30,10 @@ public class AnalyticsService implements IAnalyticsService
     private static final String ANALYTICS_SERVICE = "analytics-service";
 
     private final AnalyticsListener listener;
-    private final EventBus eventBus;
+    private final Bus eventBus;
 
     @Inject
-    public AnalyticsService(final AnalyticsListener listener, final EventBus eventBus)
+    public AnalyticsService(final AnalyticsListener listener, final Bus eventBus)
     {
         this.listener = listener;
         this.eventBus = eventBus;
@@ -51,7 +51,7 @@ public class AnalyticsService implements IAnalyticsService
         try {
             eventBus.register(listener);
         }
-        catch (EventBus.EventBusException e) {
+        catch (Bus.EventBusException e) {
             log.error("Unable to register to the EventBus!", e);
         }
     }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessAccount.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessAccount.java
index aaf75a1..7557045 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessAccount.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessAccount.java
@@ -213,7 +213,7 @@ public class BusinessAccount
         if (key != null ? !key.equals(that.key) : that.key != null) {
             return false;
         }
-        if (lastInvoiceDate != null ? !lastInvoiceDate.equals(that.lastInvoiceDate) : that.lastInvoiceDate != null) {
+        if (lastInvoiceDate != null ? lastInvoiceDate.compareTo(that.lastInvoiceDate) != 0 : that.lastInvoiceDate != null) {
             return false;
         }
         if (lastPaymentStatus != null ? !lastPaymentStatus.equals(that.lastPaymentStatus) : that.lastPaymentStatus != null) {
@@ -228,7 +228,7 @@ public class BusinessAccount
         if (totalInvoiceBalance != null ? !(Rounder.round(totalInvoiceBalance) == Rounder.round(that.totalInvoiceBalance)) : that.totalInvoiceBalance != null) {
             return false;
         }
-        if (updatedDt != null ? !updatedDt.equals(that.updatedDt) : that.updatedDt != null) {
+        if (updatedDt != null ? updatedDt.compareTo(that.updatedDt) != 0 : that.updatedDt != null) {
             return false;
         }
 
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java
index baacad8..71e3402 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java
@@ -17,7 +17,14 @@
 package com.ning.billing.analytics;
 
 import com.ning.billing.analytics.utils.Rounder;
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.Product;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.catalog.api.TimeUnit;
 import com.ning.billing.entitlement.api.user.Subscription;
 import org.joda.time.DateTime;
 import org.slf4j.Logger;
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountDaoProvider.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountDaoProvider.java
index 0a66205..a2d6b2e 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountDaoProvider.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountDaoProvider.java
@@ -16,16 +16,16 @@
 
 package com.ning.billing.analytics.dao;
 
+import org.skife.jdbi.v2.IDBI;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
-import org.skife.jdbi.v2.DBI;
 
 public class BusinessAccountDaoProvider implements Provider<BusinessAccountDao>
 {
-    private final DBI dbi;
+    private final IDBI dbi;
 
     @Inject
-    public BusinessAccountDaoProvider(final DBI dbi)
+    public BusinessAccountDaoProvider(final IDBI dbi)
     {
         this.dbi = dbi;
     }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java
index 9db1b75..374f296 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java
@@ -23,7 +23,11 @@ import org.skife.jdbi.v2.sqlobject.Binder;
 import org.skife.jdbi.v2.sqlobject.BinderFactory;
 import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
 
-import java.lang.annotation.*;
+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.sql.Types;
 
 @BindingAnnotation(BusinessSubscriptionTransitionBinder.BstBinderFactory.class)
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionDaoProvider.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionDaoProvider.java
index 86b5665..0890f84 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionDaoProvider.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionDaoProvider.java
@@ -16,16 +16,16 @@
 
 package com.ning.billing.analytics.dao;
 
+import org.skife.jdbi.v2.IDBI;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
-import org.skife.jdbi.v2.DBI;
 
 public class BusinessSubscriptionTransitionDaoProvider implements Provider<BusinessSubscriptionTransitionDao>
 {
-    private final DBI dbi;
+    private final IDBI dbi;
 
     @Inject
-    public BusinessSubscriptionTransitionDaoProvider(final DBI dbi)
+    public BusinessSubscriptionTransitionDaoProvider(final IDBI dbi)
     {
         this.dbi = dbi;
     }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java b/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
index 48663fa..25496b5 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
@@ -16,16 +16,16 @@
 
 package com.ning.billing.analytics;
 
-import org.skife.jdbi.v2.DBI;
 import org.skife.jdbi.v2.IDBI;
-
 import com.ning.billing.account.glue.AccountModule;
 import com.ning.billing.analytics.setup.AnalyticsModule;
 import com.ning.billing.catalog.glue.CatalogModule;
 import com.ning.billing.dbi.MysqlTestingHelper;
 import com.ning.billing.entitlement.glue.EntitlementModule;
+
+import com.ning.billing.util.glue.BusModule;
+
 import com.ning.billing.util.glue.ClockModule;
-import com.ning.billing.util.glue.EventBusModule;
 import com.ning.billing.util.glue.NotificationQueueModule;
 import com.ning.billing.util.glue.TagStoreModule;
 
@@ -39,7 +39,7 @@ public class AnalyticsTestModule extends AnalyticsModule
         // Need to configure a few more things for the EventBus
         install(new AccountModule());
         install(new CatalogModule());
-        install(new EventBusModule());
+        install(new BusModule());
         install(new EntitlementModule());
         install(new ClockModule());
         install(new TagStoreModule());
@@ -48,8 +48,7 @@ public class AnalyticsTestModule extends AnalyticsModule
         // Install the Dao layer
         final MysqlTestingHelper helper = new MysqlTestingHelper();
         bind(MysqlTestingHelper.class).toInstance(helper);
-        final DBI dbi = helper.getDBI();
+        final IDBI dbi = helper.getDBI();
         bind(IDBI.class).toInstance(dbi);
-        bind(DBI.class).toInstance(dbi);
     }
 }
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 621243f..395d694 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
@@ -47,7 +47,7 @@ import com.ning.billing.entitlement.api.user.SubscriptionTransition;
 import com.ning.billing.entitlement.api.user.SubscriptionTransitionData;
 import com.ning.billing.entitlement.events.EntitlementEvent;
 import com.ning.billing.entitlement.events.user.ApiEventType;
-import com.ning.billing.util.eventbus.EventBus;
+import com.ning.billing.util.bus.Bus;
 import com.ning.billing.util.tag.DescriptiveTag;
 import com.ning.billing.util.tag.DefaultTagDefinition;
 import com.ning.billing.util.tag.Tag;
@@ -90,7 +90,7 @@ public class TestAnalyticsService
     private AnalyticsService service;
 
     @Inject
-    private EventBus bus;
+    private Bus bus;
 
     @Inject
     private BusinessSubscriptionTransitionDao subscriptionDao;
diff --git a/analytics/src/test/java/com/ning/billing/analytics/dao/TestAnalyticsDao.java b/analytics/src/test/java/com/ning/billing/analytics/dao/TestAnalyticsDao.java
index 368b8c1..c17cdd4 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/dao/TestAnalyticsDao.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/dao/TestAnalyticsDao.java
@@ -16,9 +16,21 @@
 
 package com.ning.billing.analytics.dao;
 
-import com.ning.billing.analytics.*;
+import com.ning.billing.analytics.BusinessAccount;
+import com.ning.billing.analytics.BusinessSubscription;
+import com.ning.billing.analytics.BusinessSubscriptionEvent;
+import com.ning.billing.analytics.BusinessSubscriptionTransition;
+import com.ning.billing.analytics.MockDuration;
+import com.ning.billing.analytics.MockPhase;
+import com.ning.billing.analytics.MockPlan;
+import com.ning.billing.analytics.MockProduct;
 import com.ning.billing.analytics.utils.Rounder;
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.Currency;
+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;
+import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.dbi.MysqlTestingHelper;
 import com.ning.billing.entitlement.api.user.Subscription;
 import org.apache.commons.io.IOUtils;
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java b/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java
index 2012995..f10ff2f 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java
@@ -18,6 +18,8 @@ package com.ning.billing.analytics;
 
 import com.ning.billing.catalog.api.Duration;
 import com.ning.billing.catalog.api.TimeUnit;
+import org.apache.commons.lang.NotImplementedException;
+import org.joda.time.DateTime;
 
 public class MockDuration
 {
@@ -36,6 +38,11 @@ public class MockDuration
             {
                 return 1;
             }
+
+            @Override
+            public DateTime addToDateTime(DateTime dateTime) {
+                throw new NotImplementedException();
+            }
         };
     }
 
@@ -54,6 +61,11 @@ public class MockDuration
             {
                 return 1;
             }
+
+            @Override
+            public DateTime addToDateTime(DateTime dateTime) {
+                throw new NotImplementedException();
+            }
         };
     }
 
@@ -72,6 +84,11 @@ public class MockDuration
             {
                 return 1;
             }
+
+            @Override
+            public DateTime addToDateTime(DateTime dateTime) {
+                throw new NotImplementedException();
+            }
         };
     }
 }
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 3f11c0e..663ed78 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java
@@ -16,10 +16,16 @@
 
 package com.ning.billing.analytics;
 
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.BillingPeriod;
+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.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.Price;
 
 import java.math.BigDecimal;
-import java.util.Date;
 
 public class MockPhase implements PlanPhase
 {
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java b/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
index a1fdc99..ec6d9ae 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
@@ -16,13 +16,8 @@
 
 package com.ning.billing.analytics;
 
-import java.util.UUID;
 
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
-import org.testng.Assert;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
+import java.util.UUID;
 
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.PhaseType;
@@ -36,6 +31,13 @@ import com.ning.billing.entitlement.events.EntitlementEvent;
 import com.ning.billing.entitlement.events.user.ApiEventType;
 import com.ning.billing.util.clock.ClockMock;
 
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+
 public class TestAnalyticsListener
 {
     private static final String KEY = "1234";
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessAccount.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessAccount.java
index 7696723..fcf7405 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessAccount.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessAccount.java
@@ -41,7 +41,7 @@ public class TestBusinessAccount
         Assert.assertEquals(account, account);
         Assert.assertTrue(account.equals(account));
 
-        final BusinessAccount otherAccount = new BusinessAccount("pierre", BigDecimal.ONE, Collections.singletonList("batch15"), new DateTime(), BigDecimal.TEN, "ERROR_NOT_ENOUGH_FUNDS", "CreditCard", "Visa", "");
+        final BusinessAccount otherAccount = new BusinessAccount("pierre cardin", BigDecimal.ONE, Collections.singletonList("batch15"), new DateTime(), BigDecimal.TEN, "ERROR_NOT_ENOUGH_FUNDS", "CreditCard", "Visa", "");
         Assert.assertFalse(account.equals(otherAccount));
     }
 }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java
index 6c38854..55c73ad 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java
@@ -16,7 +16,12 @@
 
 package com.ning.billing.analytics;
 
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.Duration;
+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;
+import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.user.Subscription;
 import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
@@ -94,7 +99,7 @@ public class TestBusinessSubscription
         Assert.assertEquals(subscription, subscription);
         Assert.assertTrue(subscription.equals(subscription));
 
-        final Subscription otherIsubscription = new MockSubscription(Subscription.SubscriptionState.CANCELLED, plan, phase);
-        Assert.assertTrue(!subscription.equals(new BusinessSubscription(otherIsubscription, USD)));
+        final Subscription otherSubscription = new MockSubscription(Subscription.SubscriptionState.CANCELLED, plan, phase);
+        Assert.assertTrue(!subscription.equals(new BusinessSubscription(otherSubscription, USD)));
     }
 }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionEvent.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionEvent.java
index 5ea1950..a3ca56c 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionEvent.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionEvent.java
@@ -16,7 +16,11 @@
 
 package com.ning.billing.analytics;
 
-import com.ning.billing.catalog.api.*;
+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;
+import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.user.Subscription;
 import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java
index 592d20d..a8a954c 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java
@@ -16,7 +16,11 @@
 
 package com.ning.billing.analytics;
 
-import com.ning.billing.catalog.api.*;
+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;
+import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.user.Subscription;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;

api/pom.xml 2(+1 -1)

diff --git a/api/pom.xml b/api/pom.xml
index 89f3a43..afbca6a 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.3-SNAPSHOT</version>
+        <version>0.1.5-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-api</artifactId>
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountChangeNotification.java b/api/src/main/java/com/ning/billing/account/api/AccountChangeNotification.java
index 9e5f254..4102447 100644
--- a/api/src/main/java/com/ning/billing/account/api/AccountChangeNotification.java
+++ b/api/src/main/java/com/ning/billing/account/api/AccountChangeNotification.java
@@ -16,12 +16,12 @@
 
 package com.ning.billing.account.api;
 
-import com.ning.billing.util.eventbus.EventBusNotification;
+import com.ning.billing.util.bus.BusEvent;
 
 import java.util.List;
 import java.util.UUID;
 
-public interface AccountChangeNotification extends EventBusNotification {
+public interface AccountChangeNotification extends BusEvent {
     public UUID getAccountId();
 
     public List<ChangedField> getChangedFields();
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountCreationNotification.java b/api/src/main/java/com/ning/billing/account/api/AccountCreationNotification.java
index bc2c065..b30d290 100644
--- a/api/src/main/java/com/ning/billing/account/api/AccountCreationNotification.java
+++ b/api/src/main/java/com/ning/billing/account/api/AccountCreationNotification.java
@@ -16,11 +16,11 @@
 
 package com.ning.billing.account.api;
 
-import com.ning.billing.util.eventbus.EventBusNotification;
+import com.ning.billing.util.bus.BusEvent;
 
 import java.util.UUID;
 
-public interface AccountCreationNotification extends EventBusNotification {
+public interface AccountCreationNotification extends BusEvent {
     public UUID getId();
 
     public AccountData getData();
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 fcdbcd9..e208c83 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
@@ -19,7 +19,6 @@ package com.ning.billing.account.api;
 import java.util.List;
 import java.util.UUID;
 import com.ning.billing.util.customfield.CustomField;
-import com.ning.billing.util.eventbus.EventBus;
 import com.ning.billing.util.tag.Tag;
 
 public interface AccountUserApi {
diff --git a/api/src/main/java/com/ning/billing/catalog/api/Duration.java b/api/src/main/java/com/ning/billing/catalog/api/Duration.java
index f9fe583..9025367 100644
--- a/api/src/main/java/com/ning/billing/catalog/api/Duration.java
+++ b/api/src/main/java/com/ning/billing/catalog/api/Duration.java
@@ -16,10 +16,13 @@
 
 package com.ning.billing.catalog.api;
 
+import org.joda.time.DateTime;
+
 public interface Duration {
 
 	public abstract TimeUnit getUnit();
 
 	public abstract int getNumber();
 
+    public DateTime addToDateTime(DateTime dateTime);
 }
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/config/InvoiceConfig.java b/api/src/main/java/com/ning/billing/config/InvoiceConfig.java
new file mode 100644
index 0000000..a2b4270
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/config/InvoiceConfig.java
@@ -0,0 +1,39 @@
+/*
+ * 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.config;
+
+import org.skife.config.Config;
+import org.skife.config.Default;
+
+public interface InvoiceConfig {
+
+    @Config("killbill.invoice.dao.claim.time")
+    @Default("60000")
+    public long getDaoClaimTimeMs();
+
+    @Config("killbill.invoice.dao.ready.max")
+    @Default("10")
+    public int getDaoMaxReadyEvents();
+
+    @Config("killbill.invoice.engine.notifications.sleep")
+    @Default("500")
+    public long getNotificationSleepTimeMs();
+
+    @Config("killbill.invoice.engine.events.off")
+    @Default("false")
+    public boolean isEventProcessingOff();
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingEvent.java b/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingEvent.java
index d3425bb..2628b36 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingEvent.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingEvent.java
@@ -16,17 +16,14 @@
 
 package com.ning.billing.entitlement.api.billing;
 
-import java.math.BigDecimal;
-
 import org.joda.time.DateTime;
 
 import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.catalog.api.CatalogApiException;
-import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.InternationalPrice;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
 
 public interface BillingEvent extends Comparable<BillingEvent> {
 
@@ -93,19 +90,8 @@ public interface BillingEvent extends Comparable<BillingEvent> {
      */
     public InternationalPrice getRecurringPrice();
 
-    /**
-     * Syntactic sugar to wrap currency access call
-     * 
-     * @param currency
-     * @return price value
-     */
-    public BigDecimal getRecurringPrice(Currency currency) throws CatalogApiException ;
-
-    /**
-     * Syntactic sugar to wrap currency access call
-     * 
-     * @param currency
-     * @return price value
-     */
-    public BigDecimal getFixedPrice(Currency currency) throws CatalogApiException ;
+	/**
+	 * @return the transition type of the underlying subscription event that triggered this
+	 */
+	public SubscriptionTransitionType getTransitionType();
 }
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApi.java b/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApi.java
index 2b07182..6dc3e94 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApi.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApi.java
@@ -16,13 +16,12 @@
 
 package com.ning.billing.entitlement.api.billing;
 
-import java.util.List;
 import java.util.SortedSet;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
-
-import com.ning.billing.account.api.Account;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 
 public interface EntitlementBillingApi {
 
@@ -35,7 +34,10 @@ public interface EntitlementBillingApi {
      */
     public SortedSet<BillingEvent> getBillingEventsForAccount(UUID accountId);
 
+    public UUID getAccountIdFromSubscriptionId(UUID subscriptionId);
 
     public void setChargedThroughDate(UUID subscriptionId, DateTime ctd);
 
+    public void setChargedThroughDateFromTransaction(Transmogrifier transactionalDao, UUID subscriptionId, DateTime ctd);
+
 }
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApiException.java b/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApiException.java
index 9c22915..4c20f7c 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApiException.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApiException.java
@@ -16,25 +16,21 @@
 
 package com.ning.billing.entitlement.api.billing;
 
-public class EntitlementBillingApiException extends Exception {
+import com.ning.billing.BillingExceptionBase;
+import com.ning.billing.ErrorCode;
 
+public class EntitlementBillingApiException extends BillingExceptionBase {
     private static final long serialVersionUID = 127392038L;
 
-    public EntitlementBillingApiException() {
-        super();
+    public EntitlementBillingApiException(Throwable cause, int code, final String msg) {
+        super(cause, code, msg);
     }
 
-    public EntitlementBillingApiException(String msg, Throwable arg1) {
-        super(msg, arg1);
+    public EntitlementBillingApiException(Throwable cause, ErrorCode code, final Object... args) {
+        super(cause, code, args);
     }
 
-    public EntitlementBillingApiException(String msg) {
-        super(msg);
+    public EntitlementBillingApiException(ErrorCode code, final Object... args) {
+        super(code, args);
     }
-
-    public EntitlementBillingApiException(Throwable msg) {
-        super(msg);
-    }
-
-
 }
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransition.java b/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransition.java
index 26ce81f..f4c971d 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransition.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransition.java
@@ -19,12 +19,12 @@ package com.ning.billing.entitlement.api.user;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
-import com.ning.billing.util.eventbus.EventBusNotification;
+import com.ning.billing.util.bus.BusEvent;
 import org.joda.time.DateTime;
 
 import java.util.UUID;
 
-public interface SubscriptionTransition extends EventBusNotification {
+public interface SubscriptionTransition extends BusEvent {
 
     public enum SubscriptionTransitionType {
         MIGRATE_ENTITLEMENT,
diff --git a/api/src/main/java/com/ning/billing/ErrorCode.java b/api/src/main/java/com/ning/billing/ErrorCode.java
index d1f5ed9..4e1ffaa 100644
--- a/api/src/main/java/com/ning/billing/ErrorCode.java
+++ b/api/src/main/java/com/ning/billing/ErrorCode.java
@@ -43,7 +43,10 @@ public enum ErrorCode {
     ENT_CANCEL_BAD_STATE(1031, "Subscription %s is in state %s"),
     /* Un-cancellation */
     ENT_UNCANCEL_BAD_STATE(1070, "Subscription %s was not in a cancelled state"),
-
+    /* Fetch */
+    ENT_GET_NO_BUNDLE_FOR_SUBSCRIPTION(1080, "Could not find a bundle for subscription %s"),
+    ENT_GET_INVALID_BUNDLE_ID(1081, "Could not find a bundle matching id %s"),
+    ENT_INVALID_SUBSCRIPTION_ID(1082, "Unknown subscription %s"),
     /*
     *
     * Range 2000 : CATALOG
@@ -66,7 +69,7 @@ public enum ErrorCode {
     CAT_NO_PRICE_FOR_CURRENCY(2010, "This price does not have a value for the currency '%s'."),
 
     /* Price value explicitly set to NULL meaning there is no price available in that currency */
-    CAT_PRICE_VALUE_NULL_FOR_CURRENCY(2011, "The value for the currency '%s' is NULL. This plan cannot be bought in this currnency."),
+    CAT_PRICE_VALUE_NULL_FOR_CURRENCY(2011, "The value for the currency '%s' is NULL. This plan cannot be bought in this currency."),
     CAT_NULL_PRICE_LIST_NAME(2012,"Price list name was null"),
     CAT_PRICE_LIST_NOT_FOUND(2013, "Could not find a pricelist with name '%s'"),
     /*
@@ -116,8 +119,16 @@ public enum ErrorCode {
     TAG_DEFINITION_CONFLICTS_WITH_CONTROL_TAG(3900, "The tag definition name conflicts with a reserved name (name %s)"),
     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)")
-
+    TAG_DEFINITION_IN_USE(3903, "The tag definition name is currently in use (name: %s)"),
+   
+   /*
+    *
+    * Range 4000: INVOICE
+    *
+    */
+    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")  
     ;
 
     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 f69b934..0d13363 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
@@ -25,14 +25,22 @@ import java.util.List;
 import java.util.UUID;
 
 public interface Invoice extends Entity {
-    boolean add(InvoiceItem item);
+    boolean addInvoiceItem(InvoiceItem item);
 
-    boolean add(List<InvoiceItem> items);
+    boolean addInvoiceItems(List<InvoiceItem> items);
 
-    List<InvoiceItem> getItems();
+    List<InvoiceItem> getInvoiceItems();
 
     int getNumberOfItems();
 
+    boolean addPayment(InvoicePayment payment);
+
+    boolean addPayments(List<InvoicePayment> payments);
+
+    List<InvoicePayment> getPayments();
+
+    int getNumberOfPayments();
+
     UUID getAccountId();
 
     DateTime getInvoiceDate();
@@ -47,7 +55,7 @@ public interface Invoice extends Entity {
 
     BigDecimal getTotalAmount();
 
-    BigDecimal getAmountOutstanding();
+    BigDecimal getBalance();
 
     boolean isDueForPayment(DateTime targetDate, int numberOfDays);
 }
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceApiException.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceApiException.java
new file mode 100644
index 0000000..a9275e8
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceApiException.java
@@ -0,0 +1,34 @@
+/*
+ * 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.api;
+
+import com.ning.billing.BillingExceptionBase;
+import com.ning.billing.ErrorCode;
+
+public class InvoiceApiException extends BillingExceptionBase {
+    public InvoiceApiException(Throwable cause, int code, final String msg) {
+        super(cause, code, msg);
+    }
+
+    public InvoiceApiException(Throwable cause, ErrorCode code, final Object... args) {
+        super(cause, code, args);
+    }
+
+    public InvoiceApiException(ErrorCode code, final Object... args) {
+        super(code, args);
+    }
+}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceCreationNotification.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceCreationNotification.java
index 5ecd311..478a1fb 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceCreationNotification.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceCreationNotification.java
@@ -22,9 +22,9 @@ import java.util.UUID;
 import org.joda.time.DateTime;
 
 import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.util.eventbus.EventBusNotification;
+import com.ning.billing.util.bus.BusEvent;
 
-public interface InvoiceCreationNotification extends EventBusNotification {
+public interface InvoiceCreationNotification extends BusEvent {
     public UUID getInvoiceId();
     public UUID getAccountId();
     public BigDecimal getAmountOwed();
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 44c46a7..8cadd9a 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
@@ -28,15 +28,19 @@ public interface InvoiceItem extends Entity, Comparable<InvoiceItem> {
 
     UUID getSubscriptionId();
 
+    String getPlanName();
+
+    String getPhaseName();
+
     DateTime getStartDate();
 
     DateTime getEndDate();
 
-    String getDescription();
+    BigDecimal getRecurringAmount();
 
-    BigDecimal getAmount();
+    BigDecimal getRecurringRate();
 
-    BigDecimal getRate();
+    BigDecimal getFixedAmount();
 
     Currency getCurrency();
 
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoicePayment.java b/api/src/main/java/com/ning/billing/invoice/api/InvoicePayment.java
new file mode 100644
index 0000000..b947762
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoicePayment.java
@@ -0,0 +1,39 @@
+/*
+ * 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.api;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+import org.joda.time.DateTime;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.entity.Entity;
+
+public interface InvoicePayment {
+    UUID getPaymentAttemptId();
+
+    UUID getInvoiceId();
+
+    DateTime getPaymentAttemptDate();
+
+    BigDecimal getAmount();
+
+    Currency getCurrency();
+
+    DateTime getCreatedDate();
+
+    DateTime getUpdatedDate();
+}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java b/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java
index aaa89a5..641afa6 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java
@@ -23,7 +23,6 @@ import java.util.UUID;
 import org.joda.time.DateTime;
 
 import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.payment.api.InvoicePayment;
 
 public interface InvoicePaymentApi {
 
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java
index d826d5c..363e483 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java
@@ -16,23 +16,27 @@
 
 package com.ning.billing.invoice.api;
 
+import org.joda.time.DateTime;
+
 import java.math.BigDecimal;
+import java.util.Collection;
 import java.util.List;
 import java.util.UUID;
 
-import org.joda.time.DateTime;
-
-import com.ning.billing.payment.api.InvoicePayment;
-
 public interface InvoiceUserApi {
     public List<UUID> getInvoicesForPayment(DateTime targetDate, int numberOfDays);
 
     public List<Invoice> getInvoicesByAccount(UUID accountId);
 
+    public List<Invoice> getInvoicesByAccount(UUID accountId, DateTime fromDate);
+
+    public BigDecimal getAccountBalance(UUID accountId);
+
+    public List<InvoiceItem> getInvoiceItemsByAccount(UUID accountId);
+
     public Invoice getInvoice(UUID invoiceId);
 
     public void notifyOfPaymentAttempt(InvoicePayment invoicePayment);
 
-    public BigDecimal getAccountBalance(UUID accountId);
-
+    public Collection<Invoice> getUnpaidInvoicesByAccountId(UUID accountId, DateTime upToDate);
 }
diff --git a/api/src/main/java/com/ning/billing/invoice/api/test/InvoiceTestApi.java b/api/src/main/java/com/ning/billing/invoice/api/test/InvoiceTestApi.java
new file mode 100644
index 0000000..9edcd8c
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/invoice/api/test/InvoiceTestApi.java
@@ -0,0 +1,23 @@
+/*
+ * 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.api.test;
+
+import com.ning.billing.invoice.api.Invoice;
+
+public interface InvoiceTestApi {
+    public void create(Invoice invoice);
+}
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 189e87c..8ecae71 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
@@ -49,4 +49,8 @@ public interface PaymentApi {
 
     PaymentAttempt getPaymentAttemptForPaymentId(String id);
 
+    List<PaymentInfo> getPaymentInfo(List<String> invoiceIds);
+
+    PaymentAttempt getPaymentAttemptForInvoiceId(String invoiceId);
+
 }
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentAttempt.java b/api/src/main/java/com/ning/billing/payment/api/PaymentAttempt.java
index d708be3..fcccf9b 100644
--- a/api/src/main/java/com/ning/billing/payment/api/PaymentAttempt.java
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentAttempt.java
@@ -99,7 +99,7 @@ public class PaymentAttempt {
     }
 
     public PaymentAttempt(UUID paymentAttemptId, Invoice invoice) {
-        this(paymentAttemptId, invoice.getId(), invoice.getAccountId(), invoice.getAmountOutstanding(), invoice.getCurrency(), invoice.getInvoiceDate(), null, null, null, null);
+        this(paymentAttemptId, invoice.getId(), invoice.getAccountId(), invoice.getBalance(), invoice.getCurrency(), invoice.getInvoiceDate(), null, null, null, null);
     }
 
     public DateTime getInvoiceDate() {
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentError.java b/api/src/main/java/com/ning/billing/payment/api/PaymentError.java
index 45e8555..f1474c8 100644
--- a/api/src/main/java/com/ning/billing/payment/api/PaymentError.java
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentError.java
@@ -18,10 +18,10 @@ package com.ning.billing.payment.api;
 import org.codehaus.jackson.annotate.JsonTypeInfo;
 import org.codehaus.jackson.annotate.JsonTypeInfo.Id;
 
-import com.ning.billing.util.eventbus.EventBusNotification;
+import com.ning.billing.util.bus.BusEvent;
 
 @JsonTypeInfo(use = Id.NAME, property = "error")
-public class PaymentError implements EventBusNotification {
+public class PaymentError implements BusEvent {
     private final String type;
     private final String message;
 
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentInfo.java b/api/src/main/java/com/ning/billing/payment/api/PaymentInfo.java
index d67f93c..943c5f7 100644
--- a/api/src/main/java/com/ning/billing/payment/api/PaymentInfo.java
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentInfo.java
@@ -24,9 +24,9 @@ import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 
 import com.google.common.base.Objects;
-import com.ning.billing.util.eventbus.EventBusNotification;
+import com.ning.billing.util.bus.BusEvent;
 
-public class PaymentInfo implements EventBusNotification {
+public class PaymentInfo implements BusEvent {
     private final String paymentId;
     private final BigDecimal amount;
     private final BigDecimal refundAmount;
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentService.java b/api/src/main/java/com/ning/billing/payment/api/PaymentService.java
index 988a00a..ede2506 100644
--- a/api/src/main/java/com/ning/billing/payment/api/PaymentService.java
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentService.java
@@ -23,4 +23,5 @@ public interface PaymentService extends KillbillService {
     String getName();
 
     PaymentApi getPaymentApi();
+
 }

beatrix/pom.xml 14(+12 -2)

diff --git a/beatrix/pom.xml b/beatrix/pom.xml
index 9e2fdba..ded6f0f 100644
--- a/beatrix/pom.xml
+++ b/beatrix/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.3-SNAPSHOT</version>
+        <version>0.1.5-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-beatrix</artifactId>
@@ -28,7 +28,11 @@
             <groupId>com.ning.billing</groupId>
             <artifactId>killbill-entitlement</artifactId>
         </dependency>
-       <dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-invoice</artifactId>
+        </dependency>
+        <dependency>
             <groupId>com.ning.billing</groupId>
             <artifactId>killbill-catalog</artifactId>
         </dependency>
@@ -78,6 +82,12 @@
             <artifactId>commons-io</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-util</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
     <build>
         <plugins>
diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/glue/BeatrixModule.java b/beatrix/src/main/java/com/ning/billing/beatrix/glue/BeatrixModule.java
index 7a672c1..02da6c9 100644
--- a/beatrix/src/main/java/com/ning/billing/beatrix/glue/BeatrixModule.java
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/glue/BeatrixModule.java
@@ -17,13 +17,14 @@
 package com.ning.billing.beatrix.glue;
 
 import com.google.inject.AbstractModule;
+import com.ning.billing.beatrix.lifecycle.DefaultLifecycle;
 import com.ning.billing.beatrix.lifecycle.Lifecycle;
 
 public class BeatrixModule extends AbstractModule {
 
     @Override
     protected void configure() {
-        bind(Lifecycle.class).asEagerSingleton();
+        bind(Lifecycle.class).to(DefaultLifecycle.class).asEagerSingleton();
     }
 
 }
diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/DefaultLifecycle.java b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/DefaultLifecycle.java
new file mode 100644
index 0000000..8a07355
--- /dev/null
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/DefaultLifecycle.java
@@ -0,0 +1,177 @@
+/*
+ * 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.beatrix.lifecycle;
+
+import com.google.common.base.Supplier;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.ning.billing.lifecycle.KillbillService;
+import com.ning.billing.lifecycle.LifecycleHandlerType;
+import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
+import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel.Sequence;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+
+public class DefaultLifecycle implements Lifecycle {
+
+    private final static Logger log = LoggerFactory.getLogger(DefaultLifecycle.class);
+    private final SetMultimap<LifecycleLevel, LifecycleHandler<? extends KillbillService>> handlersByLevel;
+
+    private final ServiceFinder serviceFinder;
+
+    protected final Injector injector;
+
+    @Inject
+    public DefaultLifecycle(Injector injector) {
+
+        this.serviceFinder = new ServiceFinder(DefaultLifecycle.class.getClassLoader());
+        this.handlersByLevel = Multimaps.newSetMultimap(new ConcurrentHashMap<LifecycleLevel, Collection<LifecycleHandler<? extends KillbillService>>>(),
+
+                new Supplier<Set<LifecycleHandler<? extends KillbillService>>>() {
+            @Override
+            public Set<LifecycleHandler<? extends KillbillService>> get() {
+                return new CopyOnWriteArraySet<LifecycleHandler<? extends KillbillService>>();
+            }
+        });
+        this.injector = injector;
+
+        init();
+    }
+
+
+    @Override
+    public void fireStartupSequencePriorEventRegistration() {
+        fireSequence(Sequence.STARTUP_PRE_EVENT_REGISTRATION);
+    }
+
+    @Override
+    public void fireStartupSequencePostEventRegistration() {
+        fireSequence(Sequence.STARTUP_POST_EVENT_REGISTRATION);
+    }
+
+    @Override
+    public void fireShutdownSequencePriorEventUnRegistration() {
+        fireSequence(Sequence.SHUTDOWN_PRE_EVENT_UNREGISTRATION);
+    }
+
+    @Override
+    public void fireShutdownSequencePostEventUnRegistration() {
+        fireSequence(Sequence.SHUTDOWN_POST_EVENT_UNREGISTRATION);
+    }
+
+    protected Set<? extends KillbillService> findServices() {
+
+        Set<KillbillService> result = new HashSet<KillbillService>();
+        Set<Class<? extends KillbillService>> services =  serviceFinder.getServices();
+        for (Class<? extends KillbillService> cur : services) {
+            log.debug("Found service {}", cur.getName());
+            try {
+                KillbillService instance = injector.getInstance(cur);
+                log.debug("got instance {}", instance.getName());
+                result.add(instance);
+            } catch (Exception e) {
+                logWarn("Failed to inject " + cur.getName(), e);
+            }
+
+        }
+        return result;
+    }
+
+    private void init() {
+        Set<? extends KillbillService> services = findServices();
+        Iterator<? extends KillbillService> it = services.iterator();
+        while (it.hasNext()) {
+            handlersByLevel.putAll(findAllHandlers(it.next()));
+        }
+    }
+
+    private void fireSequence(Sequence seq) {
+        List<LifecycleLevel> levels = LifecycleLevel.getLevelsForSequence(seq);
+        for (LifecycleLevel cur : levels) {
+            doFireStage(cur);
+        }
+    }
+
+    private void doFireStage(LifecycleLevel level) {
+        log.info("Killbill lifecycle firing stage {}", level);
+        Set<LifecycleHandler<? extends KillbillService>> handlers = handlersByLevel.get(level);
+        for (LifecycleHandler<? extends KillbillService> cur : handlers) {
+
+            try {
+                Method method = cur.getMethod();
+                KillbillService target = cur.getTarget();
+                log.info("Killbill lifecycle calling handler {} for service {}", cur.getMethod().getName(), target.getName());
+                method.invoke(target);
+            } catch (Exception e) {
+                logWarn("Killbill lifecycle failed to invoke lifecycle handler", e);
+            }
+        }
+
+    }
+
+
+    // Used to disable valid injection failure from unit tests
+    protected void logWarn(String msg, Exception e) {
+        log.warn(msg, e);
+    }
+
+    private Multimap<LifecycleLevel, LifecycleHandler<? extends KillbillService>> findAllHandlers(KillbillService service) {
+        Multimap<LifecycleLevel, LifecycleHandler<? extends KillbillService>> methodsInService = HashMultimap.create();
+        Class<? extends KillbillService> clazz = service.getClass();
+        for (Method method : clazz.getMethods()) {
+            LifecycleHandlerType annotation = method.getAnnotation(LifecycleHandlerType.class);
+            if (annotation != null) {
+                LifecycleLevel level = annotation.value();
+                LifecycleHandler<? extends KillbillService> handler = new  LifecycleHandler<KillbillService>(service, method);
+                methodsInService.put(level, handler);
+            }
+        }
+        return methodsInService;
+    }
+
+    private final class LifecycleHandler<T> {
+        private final T target;
+        private final Method method;
+
+        public LifecycleHandler(T target, Method method) {
+            this.target = target;
+            this.method = method;
+        }
+
+        public T getTarget() {
+            return target;
+        }
+
+        public Method getMethod() {
+            return method;
+        }
+    }
+}
diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/Lifecycle.java b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/Lifecycle.java
index c0edf2c..8192a65 100644
--- a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/Lifecycle.java
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/Lifecycle.java
@@ -16,156 +16,14 @@
 
 package com.ning.billing.beatrix.lifecycle;
 
-import com.google.common.base.Supplier;
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.Multimaps;
-import com.google.common.collect.SetMultimap;
-import com.google.inject.Inject;
-import com.google.inject.Injector;
-import com.ning.billing.lifecycle.KillbillService;
-import com.ning.billing.lifecycle.LifecycleHandlerType;
-import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
-import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel.Sequence;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
-import java.lang.reflect.Method;
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArraySet;
+public interface Lifecycle {
 
+    public void fireStartupSequencePriorEventRegistration();
 
-public class Lifecycle {
+    public void fireStartupSequencePostEventRegistration();
 
-    private final static Logger log = LoggerFactory.getLogger(Lifecycle.class);
-    private final SetMultimap<LifecycleLevel, LifecycleHandler<? extends KillbillService>> handlersByLevel;
+    public void fireShutdownSequencePriorEventUnRegistration();
 
-    private final ServiceFinder serviceFinder;
-
-    private final Injector injector;
-
-    @Inject
-    public Lifecycle(Injector injector) {
-
-        this.serviceFinder = new ServiceFinder(Lifecycle.class.getClassLoader());
-        this.handlersByLevel = Multimaps.newSetMultimap(new ConcurrentHashMap<LifecycleLevel, Collection<LifecycleHandler<? extends KillbillService>>>(),
-
-                new Supplier<Set<LifecycleHandler<? extends KillbillService>>>() {
-            @Override
-            public Set<LifecycleHandler<? extends KillbillService>> get() {
-                return new CopyOnWriteArraySet<LifecycleHandler<? extends KillbillService>>();
-            }
-        });
-        this.injector = injector;
-
-        init();
-    }
-
-    public void init() {
-        Set<? extends KillbillService> services = findServices();
-        Iterator<? extends KillbillService> it = services.iterator();
-        while (it.hasNext()) {
-            handlersByLevel.putAll(findAllHandlers(it.next()));
-        }
-    }
-
-
-    public void fireStartupSequencePriorEventRegistration() {
-        fireSequence(Sequence.STARTUP_PRE_EVENT_REGISTRATION);
-    }
-
-    public void fireStartupSequencePostEventRegistration() {
-        fireSequence(Sequence.STARTUP_POST_EVENT_REGISTRATION);
-    }
-
-    public void fireShutdownSequencePriorEventUnRegistration() {
-        fireSequence(Sequence.SHUTDOWN_PRE_EVENT_UNREGISTRATION);
-    }
-
-    public void fireShutdownSequencePostEventUnRegistration() {
-        fireSequence(Sequence.SHUTDOWN_POST_EVENT_UNREGISTRATION);
-    }
-
-    private void fireSequence(Sequence seq) {
-        List<LifecycleLevel> levels = LifecycleLevel.getLevelsForSequence(seq);
-        for (LifecycleLevel cur : levels) {
-            doFireStage(cur);
-        }
-    }
-
-    private void doFireStage(LifecycleLevel level) {
-        log.info("Killbill lifecycle firing stage {}", level);
-        Set<LifecycleHandler<? extends KillbillService>> handlers = handlersByLevel.get(level);
-        for (LifecycleHandler<? extends KillbillService> cur : handlers) {
-
-            try {
-                Method method = cur.getMethod();
-                KillbillService target = cur.getTarget();
-                log.info("Killbill lifecycle calling handler {} for service {}", cur.getMethod().getName(), target.getName());
-                method.invoke(target);
-            } catch (Exception e) {
-                logWarn("Killbill lifecycle failed to invoke lifecycle handler", e);
-            }
-        }
-
-    }
-
-
-    private Set<? extends KillbillService> findServices() {
-
-        Set<KillbillService> result = new HashSet<KillbillService>();
-        Set<Class<? extends KillbillService>> services =  serviceFinder.getServices();
-        for (Class<? extends KillbillService> cur : services) {
-            log.debug("Found service {}", cur.getName());
-            try {
-                KillbillService instance = injector.getInstance(cur);
-                log.debug("got instance {}", instance.getName());
-                result.add(instance);
-            } catch (Exception e) {
-                logWarn("Failed to inject " + cur.getName(), e);
-            }
-
-        }
-        return result;
-    }
-
-
-    // Used to disable valid injection failure from unit tests
-    protected void logWarn(String msg, Exception e) {
-        log.warn(msg, e);
-    }
-
-    public Multimap<LifecycleLevel, LifecycleHandler<? extends KillbillService>> findAllHandlers(KillbillService service) {
-        Multimap<LifecycleLevel, LifecycleHandler<? extends KillbillService>> methodsInService = HashMultimap.create();
-        Class<? extends KillbillService> clazz = service.getClass();
-        for (Method method : clazz.getMethods()) {
-            LifecycleHandlerType annotation = method.getAnnotation(LifecycleHandlerType.class);
-            if (annotation != null) {
-                LifecycleLevel level = annotation.value();
-                LifecycleHandler<? extends KillbillService> handler = new  LifecycleHandler<KillbillService>(service, method);
-                methodsInService.put(level, handler);
-            }
-        }
-        return methodsInService;
-    }
-
-
-    private final class LifecycleHandler<T> {
-        private final T target;
-        private final Method method;
-
-        public LifecycleHandler(T target, Method method) {
-            this.target = target;
-            this.method = method;
-        }
-
-        public T getTarget() {
-            return target;
-        }
-
-        public Method getMethod() {
-            return method;
-        }
-    }
+    public void fireShutdownSequencePostEventUnRegistration();
 }
diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ServiceFinder.java b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ServiceFinder.java
index a4a4ce2..83115de 100644
--- a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ServiceFinder.java
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ServiceFinder.java
@@ -16,7 +16,6 @@
 
 package com.ning.billing.beatrix.lifecycle;
 
-
 import com.ning.billing.lifecycle.KillbillService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -25,7 +24,13 @@ import java.io.File;
 import java.io.IOException;
 import java.net.MalformedURLException;
 import java.net.URL;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
 import java.util.jar.JarFile;
 
 public class ServiceFinder {
@@ -81,8 +86,6 @@ public class ServiceFinder {
 	    }
 
 	    for (int h = 0; h < classPaths.length; h++) {
-
-
 	        Enumeration<?> files = null;
 	        JarFile module = null;
 	        File classPath = new File( (URL.class).isInstance(classPaths[h]) ?
@@ -93,7 +96,7 @@ public class ServiceFinder {
 
 	            List<String> dirListing = new ArrayList<String>();
 	            recursivelyListDir(dirListing, classPath, new StringBuffer() );
-	            files = Collections.enumeration( dirListing );
+	            files = Collections.enumeration(dirListing);
 	        } else if (classPath.getName().endsWith(".jar")) {
 
 	            log.debug("JAR : " + classPath);
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/MockModule.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/MockModule.java
new file mode 100644
index 0000000..28294f9
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/MockModule.java
@@ -0,0 +1,104 @@
+/*
+ * 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.beatrix.integration.inv_ent;
+
+import static org.testng.Assert.assertNotNull;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Set;
+
+import org.skife.config.ConfigurationObjectFactory;
+import org.skife.jdbi.v2.IDBI;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.ning.billing.account.glue.AccountModule;
+import com.ning.billing.beatrix.lifecycle.DefaultLifecycle;
+import com.ning.billing.beatrix.lifecycle.Lifecycle;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.catalog.glue.CatalogModule;
+import com.ning.billing.dbi.DBIProvider;
+import com.ning.billing.dbi.DbiConfig;
+import com.ning.billing.entitlement.api.EntitlementService;
+import com.ning.billing.entitlement.glue.EntitlementModule;
+import com.ning.billing.invoice.api.InvoiceService;
+import com.ning.billing.invoice.glue.InvoiceModule;
+import com.ning.billing.lifecycle.KillbillService;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+import com.ning.billing.util.bus.BusService;
+import com.ning.billing.util.glue.BusModule;
+import com.ning.billing.util.glue.GlobalLockerModule;
+import com.ning.billing.util.glue.NotificationQueueModule;
+
+
+public class MockModule extends AbstractModule {
+
+    @Override
+    protected void configure() {
+
+        loadSystemPropertiesFromClasspath("/resource.properties");
+
+        bind(Clock.class).to(ClockMock.class).asEagerSingleton();
+        bind(ClockMock.class).asEagerSingleton();
+        bind(Lifecycle.class).to(SubsetDefaultLifecycle.class).asEagerSingleton();
+        bind(IDBI.class).toProvider(DBIProvider.class).asEagerSingleton();
+        final DbiConfig config = new ConfigurationObjectFactory(System.getProperties()).build(DbiConfig.class);
+        bind(DbiConfig.class).toInstance(config);
+        install(new BusModule());
+        install(new NotificationQueueModule());
+        install(new AccountModule());
+        install(new CatalogModule());
+        install(new EntitlementModule());
+        install(new InvoiceModule());
+        install(new GlobalLockerModule());
+    }
+
+
+    private static void loadSystemPropertiesFromClasspath(final String resource) {
+        final URL url = TestBasic.class.getResource(resource);
+        assertNotNull(url);
+        try {
+            System.getProperties().load( url.openStream() );
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private final static class SubsetDefaultLifecycle extends DefaultLifecycle {
+
+
+        @Inject
+        public SubsetDefaultLifecycle(Injector injector) {
+            super(injector);
+        }
+
+        @Override
+        protected Set<? extends KillbillService> findServices() {
+            ImmutableSet<? extends KillbillService> services = new ImmutableSet.Builder<KillbillService>()
+                            .add(injector.getInstance(BusService.class))
+                            .add(injector.getInstance(CatalogService.class))
+                            .add(injector.getInstance(EntitlementService.class))
+                            .add(injector.getInstance(InvoiceService.class))
+                            .build();
+            return services;
+        }
+    }
+}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBasic.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBasic.java
new file mode 100644
index 0000000..1f2adce
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBasic.java
@@ -0,0 +1,426 @@
+/*
+ * 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.beatrix.integration.inv_ent;
+
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.UUID;
+
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.Interval;
+import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.TransactionCallback;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.AfterSuite;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountData;
+import com.ning.billing.account.api.AccountService;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.beatrix.integration.inv_ent.TestBusHandler.NextEvent;
+import com.ning.billing.beatrix.lifecycle.Lifecycle;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.EntitlementService;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.invoice.api.InvoiceService;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+
+import com.ning.billing.util.clock.ClockMock;
+import com.ning.billing.util.bus.BusService;
+
+@Guice(modules = {MockModule.class})
+public class TestBasic {
+
+    private static final Logger log = LoggerFactory.getLogger(TestBasic.class);
+    private static long AT_LEAST_ONE_MONTH_MS =  31L * 24L * 3600L * 1000L;
+
+    @Inject IDBI dbi;
+
+    @Inject
+    private ClockMock clock;
+
+    @Inject
+    private Lifecycle lifecycle;
+
+    @Inject
+    private BusService busService;
+
+    @Inject
+    private EntitlementService entitlementService;
+
+    @Inject
+    private InvoiceService invoiceService;
+
+    @Inject
+    private AccountService accountService;
+
+    private EntitlementUserApi entitlementUserApi;
+
+    private InvoiceUserApi invoiceUserApi;
+
+    private AccountUserApi accountUserApi;
+
+    private TestBusHandler busHandler;
+
+
+
+    @BeforeSuite(alwaysRun = true)
+    public void setup() throws Exception{
+
+        /**
+         * Initialize lifecyle for subset of services
+         */
+        busHandler = new TestBusHandler();
+        lifecycle.fireStartupSequencePriorEventRegistration();
+        busService.getBus().register(busHandler);
+        lifecycle.fireStartupSequencePostEventRegistration();
+
+        /**
+         * Retrieve APIs
+         */
+        entitlementUserApi = entitlementService.getUserApi();
+        invoiceUserApi = invoiceService.getUserApi();
+        accountUserApi = accountService.getAccountUserApi();
+    }
+
+    @AfterSuite(alwaysRun = true)
+    public void tearDown() throws Exception {
+        lifecycle.fireShutdownSequencePriorEventUnRegistration();
+        busService.getBus().unregister(busHandler);
+        lifecycle.fireShutdownSequencePostEventUnRegistration();
+    }
+
+
+    @BeforeMethod(alwaysRun = true)
+    public void setupTest() {
+
+        log.warn("\n");
+        log.warn("RESET TEST FRAMEWORK\n\n");
+        busHandler.reset();
+        clock.resetDeltaFromReality();
+        cleanupData();
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void cleanupTest() {
+        log.warn("DONE WITH TEST\n");
+    }
+
+    private void cleanupData() {
+        dbi.inTransaction(new TransactionCallback<Void>() {
+            @Override
+            public Void inTransaction(Handle h, TransactionStatus status)
+                    throws Exception {
+                h.execute("truncate table accounts");
+                h.execute("truncate table events");
+                h.execute("truncate table subscriptions");
+                h.execute("truncate table bundles");
+                h.execute("truncate table notifications");
+                h.execute("truncate table claimed_notifications");
+                h.execute("truncate table invoices");
+                h.execute("truncate table invoice_items");
+                h.execute("truncate table tag_definitions");
+                h.execute("truncate table tags");
+                return null;
+            }
+        });
+    }
+
+    private DateTime checkAndGetCTD(UUID subscriptionId) {
+
+        SubscriptionData subscription = (SubscriptionData) entitlementUserApi.getSubscriptionFromId(subscriptionId);
+        DateTime ctd = subscription.getChargedThroughDate();
+        assertNotNull(ctd);
+        log.info("Checking CTD: " + ctd.toString() + "; clock is " + clock.getUTCNow().toString());
+        assertTrue(clock.getUTCNow().isBefore(ctd));
+        return ctd;
+    }
+
+    @Test(groups = "fast", enabled = false)
+    public void testBasePlanCompleteWithBillingDayInPast() throws Exception {
+        testBasePlanComplete(clock.getUTCNow().minusDays(1).getDayOfMonth());
+    }
+
+    @Test(groups = "fast", enabled = false)
+    public void testBasePlanCompleteWithBillingDayPresent() throws Exception {
+        testBasePlanComplete(clock.getUTCNow().getDayOfMonth());
+    }
+
+    @Test(groups = "fast", enabled = false)
+    public void testBasePlanCompleteWithBillingDayInFuture() throws Exception {
+        testBasePlanComplete(clock.getUTCNow().plusDays(1).getDayOfMonth());
+    }
+
+    private void testBasePlanComplete(int billingDay) throws Exception {
+        long DELAY = 5000;
+
+        Account account = accountUserApi.createAccount(getAccountData(billingDay), null, null);
+        assertNotNull(account);
+
+        SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(account.getId(), "whatever");
+
+        String productName = "Shotgun";
+        BillingPeriod term = BillingPeriod.MONTHLY;
+        String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+        //
+        // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE NextEvent.INVOICE
+        //
+        busHandler.pushExpectedEvent(NextEvent.CREATE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        SubscriptionData subscription = (SubscriptionData) entitlementUserApi.createSubscription(bundle.getId(),
+                new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null);
+        assertNotNull(subscription);
+        assertTrue(busHandler.isCompleted(DELAY));
+        log.info("testSimple passed first busHandler checkpoint.");
+
+        //
+        // VERIFY CTD HAS BEEN SET
+        //
+
+        checkAndGetCTD(subscription.getId());
+
+        //
+        // CHANGE PLAN IMMEDIATELY AND EXPECT BOTH EVENTS: NextEvent.CHANGE NextEvent.INVOICE
+        //
+        busHandler.pushExpectedEvent(NextEvent.CHANGE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+
+        BillingPeriod newTerm = BillingPeriod.MONTHLY;
+        String newPlanSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+        String newProductName = "Assault-Rifle";
+        subscription.changePlan(newProductName, newTerm, newPlanSetName, clock.getUTCNow());
+
+        assertTrue(busHandler.isCompleted(DELAY));
+        log.info("testSimple passed second busHandler checkpoint.");
+
+        //
+        // VERIFY AGAIN CTD HAS BEEN SET
+        //
+        DateTime ctd = checkAndGetCTD(subscription.getId());
+
+        //
+        // MOVE TIME TO AFTER TRIAL AND EXPECT BOTH EVENTS :  NextEvent.PHASE NextEvent.INVOICE
+        //
+        busHandler.pushExpectedEvent(NextEvent.PHASE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        clock.setDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
+
+        assertTrue(busHandler.isCompleted(DELAY));
+
+        //
+        // CHANGE PLAN EOT AND EXPECT NOTHING
+        //
+        newTerm = BillingPeriod.MONTHLY;
+        newPlanSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+        newProductName = "Pistol";
+        subscription = (SubscriptionData) entitlementUserApi.getSubscriptionFromId(subscription.getId());
+        subscription.changePlan(newProductName, newTerm, newPlanSetName, clock.getUTCNow());
+        log.info("testSimple has passed third busHandler checkpoint (no events)");
+
+        //
+        // MOVE TIME AFTER CTD AND EXPECT BOTH EVENTS : NextEvent.CHANGE NextEvent.INVOICE
+        //
+        busHandler.pushExpectedEvent(NextEvent.CHANGE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        //clock.addDeltaFromReality(ctd.getMillis() - clock.getUTCNow().getMillis());
+        clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS + 1000);
+        assertTrue(busHandler.isCompleted(DELAY));
+        log.info("testSimple passed fourth busHandler checkpoint.");
+
+        //
+        // MOVE TIME AFTER NEXT BILL CYCLE DAY AND EXPECT EVENT : NextEvent.INVOICE
+        //
+        int maxCycles = 3;
+        DateTime lastCtd = null;
+        do {
+            busHandler.pushExpectedEvent(NextEvent.INVOICE);
+            clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS + 1000);
+            assertTrue(busHandler.isCompleted(DELAY));
+            lastCtd = checkAndGetCTD(subscription.getId());
+        } while (maxCycles-- > 0);
+
+        //
+        // FINALLY CANCEL SUBSCRIPTION EOT
+        //
+        subscription = (SubscriptionData) entitlementUserApi.getSubscriptionFromId(subscription.getId());
+        subscription.cancel(clock.getUTCNow(), false);
+
+        // MOVE AFTER CANCEL DATE AND EXPECT EVENT : NextEvent.CANCEL
+        busHandler.pushExpectedEvent(NextEvent.CANCEL);
+        Interval it = new Interval(clock.getUTCNow(), lastCtd);
+        clock.addDeltaFromReality(it.toDurationMillis());
+        assertTrue(busHandler.isCompleted(DELAY));
+
+        //
+        // CHECK AGAIN THERE IS NO MORE INVOICES GENERATED
+        //
+        busHandler.reset();
+        clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS + 1000);
+        assertTrue(busHandler.isCompleted(DELAY));
+
+
+        subscription = (SubscriptionData) entitlementUserApi.getSubscriptionFromId(subscription.getId());
+        lastCtd = subscription.getChargedThroughDate();
+        assertNotNull(lastCtd);
+        log.info("Checking CTD: " + lastCtd.toString() + "; clock is " + clock.getUTCNow().toString());
+        assertTrue(lastCtd.isBefore(clock.getUTCNow()));
+    }
+
+    @Test(enabled=false)
+    public void testHappyPath() throws AccountApiException, EntitlementUserApiException {
+        long DELAY = 5000 * 10;
+
+        Account account = accountUserApi.createAccount(getAccountData(3), null, null);
+        assertNotNull(account);
+
+        SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(account.getId(), "whatever");
+
+        String productName = "Shotgun";
+        BillingPeriod term = BillingPeriod.MONTHLY;
+        String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+        busHandler.pushExpectedEvent(NextEvent.CREATE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        SubscriptionData subscription = (SubscriptionData) entitlementUserApi.createSubscription(bundle.getId(),
+                new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null);
+        assertNotNull(subscription);
+
+        assertTrue(busHandler.isCompleted(DELAY));
+
+        busHandler.pushExpectedEvent(NextEvent.CHANGE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        BillingPeriod newTerm = BillingPeriod.MONTHLY;
+        String newPlanSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+        String newProductName = "Assault-Rifle";
+        subscription.changePlan(newProductName, newTerm, newPlanSetName, clock.getUTCNow());
+
+        assertTrue(busHandler.isCompleted(DELAY));
+
+        busHandler.pushExpectedEvent(NextEvent.PHASE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        clock.setDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
+        assertTrue(busHandler.isCompleted(DELAY));
+
+    }
+
+
+    protected AccountData getAccountData(final int billingDay) {
+        AccountData accountData = new AccountData() {
+            @Override
+            public String getName() {
+                return "firstName lastName";
+            }
+            @Override
+            public int getFirstNameLength() {
+                return "firstName".length();
+            }
+            @Override
+            public String getEmail() {
+                return "accountName@yahoo.com";
+            }
+            @Override
+            public String getPhone() {
+                return "4152876341";
+            }
+            @Override
+            public String getExternalKey() {
+                return "k123456";
+            }
+            @Override
+            public int getBillCycleDay() {
+                return billingDay;
+            }
+            @Override
+            public Currency getCurrency() {
+                return Currency.USD;
+            }
+            @Override
+            public String getPaymentProviderName() {
+                return "Paypal";
+            }
+
+            @Override
+            public DateTimeZone getTimeZone() {
+                return null;
+            }
+
+            @Override
+            public String getLocale() {
+                return null;
+            }
+
+            @Override
+            public String getAddress1() {
+                return null;
+            }
+
+            @Override
+            public String getAddress2() {
+                return null;
+            }
+
+            @Override
+            public String getCompanyName() {
+                return null;
+            }
+
+            @Override
+            public String getCity() {
+                return null;
+            }
+
+            @Override
+            public String getStateOrProvince() {
+                return null;
+            }
+
+            @Override
+            public String getPostalCode() {
+                return null;
+            }
+
+            @Override
+            public String getCountry() {
+                return null;
+            }
+        };
+        return accountData;
+    }
+}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBusHandler.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBusHandler.java
new file mode 100644
index 0000000..2cfd46d
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBusHandler.java
@@ -0,0 +1,169 @@
+/*
+ * 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.beatrix.integration.inv_ent;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Stack;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Joiner;
+import com.google.common.eventbus.Subscribe;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+import com.ning.billing.invoice.api.InvoiceCreationNotification;
+
+public class TestBusHandler {
+
+    protected static final Logger log = LoggerFactory.getLogger(TestBusHandler.class);
+
+    private final List<NextEvent> nextExpectedEvent;
+
+    private volatile boolean completed;
+
+    public TestBusHandler() {
+        nextExpectedEvent = new Stack<NextEvent>();
+        this.completed = false;
+    }
+
+    public enum NextEvent {
+        MIGRATE_ENTITLEMENT,
+        CREATE,
+        CHANGE,
+        CANCEL,
+        UNCANCEL,
+        PAUSE,
+        RESUME,
+        PHASE,
+        INVOICE
+    }
+
+    @Subscribe
+    public void handleEntitlementEvents(SubscriptionTransition event) {
+        log.info(String.format("TestBusHandler Got subscription event %s", event.toString()));
+        switch (event.getTransitionType()) {
+        case MIGRATE_ENTITLEMENT:
+            assertEqualsNicely(NextEvent.MIGRATE_ENTITLEMENT);
+            notifyIfStackEmpty();
+            break;
+        case CREATE:
+            assertEqualsNicely(NextEvent.CREATE);
+            notifyIfStackEmpty();
+
+            break;
+        case CANCEL:
+            assertEqualsNicely(NextEvent.CANCEL);
+            notifyIfStackEmpty();
+
+            break;
+        case CHANGE:
+            assertEqualsNicely(NextEvent.CHANGE);
+            notifyIfStackEmpty();
+
+            break;
+        case PAUSE:
+            assertEqualsNicely(NextEvent.PAUSE);
+            notifyIfStackEmpty();
+
+            break;
+        case RESUME:
+            assertEqualsNicely(NextEvent.RESUME);
+            notifyIfStackEmpty();
+
+            break;
+        case UNCANCEL:
+            assertEqualsNicely(NextEvent.UNCANCEL);
+            notifyIfStackEmpty();
+            break;
+        case PHASE:
+            assertEqualsNicely(NextEvent.PHASE);
+            notifyIfStackEmpty();
+            break;
+        default:
+            throw new RuntimeException("Unexpected event type " + event.getRequestedTransitionTime());
+        }
+    }
+
+    @Subscribe
+    public void handleInvoiceEvents(InvoiceCreationNotification event) {
+        log.info(String.format("TestBusHandler Got Invoice event %s", event.toString()));
+        assertEqualsNicely(NextEvent.INVOICE);
+        notifyIfStackEmpty();
+
+    }
+
+    public void reset() {
+        nextExpectedEvent.clear();
+        completed = true;
+    }
+
+    public void pushExpectedEvent(NextEvent next) {
+        synchronized (this) {
+            nextExpectedEvent.add(next);
+            completed = false;
+        }
+    }
+
+    public boolean isCompleted(long timeout) {
+        synchronized (this) {
+            if (completed) {
+                return completed;
+            }
+            try {
+                wait(timeout);
+            } catch (Exception ignore) {
+            }
+        }
+        if (!completed) {
+            Joiner joiner = Joiner.on(" ");
+            log.error("TestBusHandler did not complete in " + timeout + " ms, remaining events are " + joiner.join(nextExpectedEvent));
+        }
+        return completed;
+    }
+
+    private void notifyIfStackEmpty() {
+        log.debug("TestBusHandler notifyIfStackEmpty ENTER");
+        synchronized (this) {
+            if (nextExpectedEvent.isEmpty()) {
+                log.debug("notifyIfStackEmpty EMPTY");
+                completed = true;
+                notify();
+            }
+        }
+        log.debug("TestBusHandler notifyIfStackEmpty EXIT");
+    }
+
+    private void assertEqualsNicely(NextEvent received) {
+
+        boolean foundIt = false;
+        Iterator<NextEvent> it = nextExpectedEvent.iterator();
+        while (it.hasNext()) {
+            NextEvent ev = it.next();
+            if (ev == received) {
+                it.remove();
+                foundIt = true;
+                break;
+            }
+        }
+        if (!foundIt) {
+            Joiner joiner = Joiner.on(" ");
+            log.error("TestBusHandler Received event " + received + "; expected " + joiner.join(nextExpectedEvent));
+            // System.exit(1);
+        }
+    }
+}
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 69e6a0d..9bf7c10 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
@@ -16,7 +16,11 @@
 
 package com.ning.billing.beatrix.lifecycle;
 
-import com.google.inject.*;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
 import com.ning.billing.lifecycle.KillbillService;
 import com.ning.billing.lifecycle.LifecycleHandlerType;
 import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
@@ -34,7 +38,7 @@ public class TestLifecycle {
     private Service1 s1;
     private Service2 s2;
 
-    private Lifecycle lifecycle;
+    private DefaultLifecycle lifecycle;
 
     public static class ServiceBase {
 
@@ -122,7 +126,7 @@ public class TestLifecycle {
         final Injector g = Guice.createInjector(Stage.DEVELOPMENT, new TestLifecycleModule());
         s1 = g.getInstance(Service1.class);
         s2 = g.getInstance(Service2.class);
-        lifecycle = g.getInstance(Lifecycle.class);
+        lifecycle = g.getInstance(DefaultLifecycle.class);
     }
 
     @Test(enabled=true, groups={"fast"})
@@ -148,7 +152,7 @@ public class TestLifecycle {
         Assert.assertEquals(s1.getCount() + s2.getCount(), 1);
     }
 
-    public static class LifecycleNoWarn extends Lifecycle {
+    public static class LifecycleNoWarn extends DefaultLifecycle {
 
         @Inject
         public LifecycleNoWarn(Injector injector) {
@@ -163,7 +167,7 @@ public class TestLifecycle {
 
         @Override
         protected void configure() {
-            bind(Lifecycle.class).to(LifecycleNoWarn.class).asEagerSingleton();
+            bind(DefaultLifecycle.class).to(LifecycleNoWarn.class).asEagerSingleton();
             bind(Service1.class).asEagerSingleton();
             bind(Service2.class).asEagerSingleton();
         }
diff --git a/beatrix/src/test/resources/Catalog-Entitlement-Testplan.txt b/beatrix/src/test/resources/Catalog-Entitlement-Testplan.txt
new file mode 100644
index 0000000..6d5867f
--- /dev/null
+++ b/beatrix/src/test/resources/Catalog-Entitlement-Testplan.txt
@@ -0,0 +1,112 @@
+ * 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.
+
+
+NOTES
+=====
+
+Events: Create, change, cancel, migrate 
+Validate: BillingEvents, SubscriptionTransition 
+Rules: 
+    Cancellation
+        Action Policy: When to cancel (Immediate/End-of-term)
+    Creation
+        Alignment: How to align phases in a bundle
+    Change plan behavior
+        Action Policy: When to change plan (Immediate/End-of-term)
+        Alignment: How to align phases 
+        Pricelist: Which pricelist to pick when moving between plans
+    Billing alignment
+        Subscription BCD, Bundle BCD, Account BCD 
+Phases - timing
+Prices - multi-currency, fixed vs recurring prices
+Pricelists - particularly pricelist change rules
+Catalog changes new subscriptions / existing subscriptions
+Price change
+    
+TESTS
+=====
+
+BASEPLAN TESTS
+    * Create a single phase recurring plan
+        - check for creation event (timing?)
+        - check for no termination event
+        - check pricing (different currencies)
+        - check BDC (subscription, account, timezone)
+    * Create a single phase fixed length plan 
+        - check for creation event (timing - different request dates)
+        - check for termination event (timing - different lengths?)
+        - check price (fixed vs recurring)
+    * Create a two phase event use a fixed price and a recurring price
+        - check for phase change (timing)
+        - check prices change
+    * Create a multi-phase plan 
+        - check for phase events
+        - check price changes
+    * Create multiple base plans in a single bundle - should fail
+    
+    * Change base plan once
+        - check plan change policy (immediate, eot)
+        - check alignments of new plan with old 
+        - check move between pricelists
+        - check that phases progress successfully after change
+        - check obsolete events are removed
+    * Change base plan multiple times
+        - check that alignment occurs correctly 
+        - check phases progress correctly
+        - check obsolete events are removed 
+        
+    * Cancellation of a single phase plan
+        - check creation and timing of termination event
+    * Cancellation of a multi-phase plan
+        - check creation of termination event 
+        - check removal of events beyond termination event
+    * Change a cancelled base plan - should fail
+    
+    * Migration to a single phase plan
+        - check migration event occurs when it should
+    * Migration to a multi-phase plan
+        - check migration event occurs when it should
+        - check migration into different phase
+        - check alignment of phases can be correctly controlled
+    * Migration to a fixed duration plan
+        - check migration event occurs when it should
+        - check termination event occurs when it should
+        
+
+         
+STANDALONE TEST
+    * Create multiple plans in a single bundle
+        - check plans can be created
+        - check cannot add a base plan
+        - check BCD at subscription bundle level
+            
+
+PRICE CHANGE TEST
+    * Price change on a single phase base plan
+        - check new subscriptions get price after effective date
+        - check changed subscriptions get price after effective date
+        - check existing subscriptions ONLY get it after ESED
+        - check that if ESED is missing existing subs are grandfathered for ever
+    * Price change on a multi-phase subscription
+        - check price change applies correctly to correct phases
+    * Multiple price changes
+        - check multiple price changes with overlapping dates
+
+
+ADD-ON TESTS
+    * Add-on creation alignment
+    * Add-on cancel with base plan
+    
+    
\ No newline at end of file
diff --git a/beatrix/src/test/resources/catalogSample.xml b/beatrix/src/test/resources/catalogSample.xml
new file mode 100644
index 0000000..325f731
--- /dev/null
+++ b/beatrix/src/test/resources/catalogSample.xml
@@ -0,0 +1,641 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!--
+  ~ 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.
+  -->
+
+<!-- 
+Use cases covered so far:
+	Tiered Product (Pistol/Shotgun/Assault-Rifle)
+	Multiple changeEvent plan policies
+	Multiple PlanAlignment (see below, trial add-on alignments and rescue discount package)
+	Product transition rules
+	Add on (Scopes, Hoster)
+	Multi-pack addon (Extra-Ammo)
+	Addon Trial aligned to base plan (holster-monthly-regular)
+	Addon Trial aligned to creation (holster-monthly-special)
+	Rescue discount package (assault-rifle-annual-rescue)
+	Plan phase with a reccurring and a one off (refurbish-maintenance)
+	Phan with more than 2 phase (gunclub discount plans)
+		
+Use Cases to do:
+	Tiered Add On
+	Riskfree period
+	
+
+
+ -->
+<catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:noNamespaceSchemaLocation="CatalogSchema.xsd ">
+
+	<effectiveDate>2011-01-01T00:00:00+00:00</effectiveDate>
+	<catalogName>Firearms</catalogName>
+
+	<currencies>
+		<currency>USD</currency>
+		<currency>EUR</currency>
+		<currency>GBP</currency>
+	</currencies>
+	
+	<products>
+		<product name="Pistol">
+			<category>BASE</category>
+			<available>
+				<addonProduct>Telescopic-Scope</addonProduct>
+				<addonProduct>Laser-Scope</addonProduct>
+			</available>
+		</product>
+		<product name="Shotgun">
+			<category>BASE</category>
+		</product>
+		<product name="Assault-Rifle">
+			<category>BASE</category>
+			<included> 
+				<addonProduct>Telescopic-Scope</addonProduct>
+			</included>
+			<available>
+				<addonProduct>Laser-Scope</addonProduct>
+			</available>
+		</product>
+		<product name="Telescopic-Scope">
+			<category>ADD_ON</category>
+		</product>
+		<product name="Laser-Scope">
+			<category>ADD_ON</category>
+		</product>
+		<product name="Holster">
+			<category>ADD_ON</category>
+		</product>
+		<product name="Extra-Ammo">
+			<category>ADD_ON</category>
+		</product>
+		<product name="Refurbish-Maintenance">
+			<category>ADD_ON</category>
+		</product>
+	</products>
+	 
+	<rules>
+		<changePolicy>
+			<changePolicyCase> 
+				<phaseType>TRIAL</phaseType>
+				<policy>IMMEDIATE</policy>
+			</changePolicyCase>
+			<changePolicyCase> 
+				<toProduct>Pistol</toProduct>
+				<policy>END_OF_TERM</policy>
+			</changePolicyCase>
+			<changePolicyCase> 
+				<toPriceList>rescue</toPriceList>
+				<policy>END_OF_TERM</policy>
+			</changePolicyCase>		
+			<changePolicyCase> 
+				<fromProduct>Pistol</fromProduct>
+				<toProduct>Shotgun</toProduct>
+				<policy>IMMEDIATE</policy>
+			</changePolicyCase>
+			<changePolicyCase> 
+				<fromProduct>Assault-Rifle</fromProduct>
+				<toProduct>Shotgun</toProduct>
+				<policy>END_OF_TERM</policy>
+			</changePolicyCase>
+			<changePolicyCase> 
+				<fromBillingPeriod>MONTHLY</fromBillingPeriod>
+				<toProduct>Assault-Rifle</toProduct>
+				<toBillingPeriod>MONTHLY</toBillingPeriod>
+				<policy>END_OF_TERM</policy>
+			</changePolicyCase>
+			<changePolicyCase> 
+				<toProduct>Assault-Rifle</toProduct>
+				<policy>IMMEDIATE</policy>
+			</changePolicyCase>
+			<changePolicyCase> 
+				<fromBillingPeriod>MONTHLY</fromBillingPeriod>
+				<toBillingPeriod>ANNUAL</toBillingPeriod>
+				<policy>IMMEDIATE</policy>
+			</changePolicyCase>
+			<changePolicyCase> 
+				<fromBillingPeriod>ANNUAL</fromBillingPeriod>
+				<toBillingPeriod>MONTHLY</toBillingPeriod>
+				<policy>END_OF_TERM</policy>
+			</changePolicyCase>
+			<changePolicyCase> 
+				<policy>END_OF_TERM</policy>
+			</changePolicyCase>
+		</changePolicy>
+		<changeAlignment>
+			<changeAlignmentCase>
+				<alignment>START_OF_SUBSCRIPTION</alignment>
+			</changeAlignmentCase>
+			<changeAlignmentCase>
+				<toPriceList>rescue</toPriceList>
+				<alignment>CHANGE_OF_PLAN</alignment>
+			</changeAlignmentCase>
+			<changeAlignmentCase>
+				<fromPriceList>rescue</fromPriceList>
+				<toPriceList>rescue</toPriceList>
+				<alignment>CHANGE_OF_PRICELIST</alignment>
+			</changeAlignmentCase>
+		</changeAlignment>
+		<cancelPolicy>
+			<cancelPolicyCase>
+				<policy>END_OF_TERM</policy>
+			</cancelPolicyCase>
+			<cancelPolicyCase>
+				<phaseType>TRIAL</phaseType>
+				<policy>IMMEDIATE</policy>
+			</cancelPolicyCase>
+		</cancelPolicy>
+		<createAlignment>
+			<createAlignmentCase>
+				<alignment>START_OF_BUNDLE</alignment>
+			</createAlignmentCase>
+		</createAlignment>
+		<billingAlignment>
+			<billingAlignmentCase>
+				<productCategory>ADD_ON</productCategory>
+				<alignment>BUNDLE</alignment>
+			</billingAlignmentCase>
+			<billingAlignmentCase>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<alignment>SUBSCRIPTION</alignment>
+			</billingAlignmentCase>
+			<billingAlignmentCase>
+				<alignment>ACCOUNT</alignment>
+			</billingAlignmentCase>
+		</billingAlignment>
+		<priceList>
+			<priceListCase>
+				<fromPriceList>rescue</fromPriceList>
+				<toPriceList>DEFAULT</toPriceList>
+			</priceListCase>
+		</priceList>
+	</rules>
+
+	<plans>
+		<plan name="pistol-monthly-no-trial">
+			<product>Pistol</product>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>MONTHLY</billingPeriod>
+				<recurringPrice>
+					<price><currency>GBP</currency><value>29.95</value></price>
+					<price><currency>EUR</currency><value>29.95</value></price> 
+					<price><currency>USD</currency><value>29.95</value></price>								
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<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>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>MONTHLY</billingPeriod>
+				<recurringPrice>
+					<price><currency>GBP</currency><value>29.95</value></price>
+					<price><currency>EUR</currency><value>29.95</value></price> 
+					<price><currency>USD</currency><value>29.95</value></price>								
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="shotgun-monthly">
+			<product>Shotgun</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>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+					<number>-1</number>
+				</duration>
+				<billingPeriod>MONTHLY</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>249.95</value></price>								
+					<price><currency>EUR</currency><value>149.95</value></price>
+					<price><currency>GBP</currency><value>169.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="assault-rifle-monthly">
+			<product>Assault-Rifle</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>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>MONTHLY</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>599.95</value></price>								
+					<price><currency>EUR</currency><value>349.95</value></price>
+					<price><currency>GBP</currency><value>399.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="pistol-annual">
+			<product>Pistol</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice>
+					</fixedPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>199.95</value></price>								
+					<price><currency>EUR</currency><value>199.95</value></price>
+					<price><currency>GBP</currency><value>199.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="shotgun-annual">
+			<product>Shotgun</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice>
+					</fixedPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>2399.95</value></price>								
+					<price><currency>EUR</currency><value>1499.95</value></price>
+					<price><currency>GBP</currency><value>1699.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="assault-rifle-annual">
+			<product>Assault-Rifle</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice>
+					</fixedPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>5999.95</value></price>								
+					<price><currency>EUR</currency><value>3499.95</value></price>
+					<price><currency>GBP</currency><value>3999.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="pistol-annual-gunclub-discount">
+			<product>Pistol</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice>
+					</fixedPrice>
+				</phase>
+				<phase type="DISCOUNT">
+					<duration>
+						<unit>MONTHS</unit>
+						<number>6</number>
+					</duration>
+					<billingPeriod>MONTHLY</billingPeriod>
+					<recurringPrice>
+						<price><currency>USD</currency><value>9.95</value></price>								
+						<price><currency>EUR</currency><value>9.95</value></price>
+						<price><currency>GBP</currency><value>9.95</value></price>
+					</recurringPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>199.95</value></price>								
+					<price><currency>EUR</currency><value>199.95</value></price>
+					<price><currency>GBP</currency><value>199.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="shotgun-annual-gunclub-discount">
+			<product>Shotgun</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice>
+					</fixedPrice>
+				</phase>
+				<phase type="DISCOUNT">
+					<duration>
+						<unit>MONTHS</unit>
+						<number>6</number>
+					</duration>
+					<billingPeriod>MONTHLY</billingPeriod>
+					<recurringPrice>
+						<price><currency>USD</currency><value>19.95</value></price>								
+						<price><currency>EUR</currency><value>49.95</value></price>
+						<price><currency>GBP</currency><value>69.95</value></price>
+					</recurringPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>2399.95</value></price>								
+					<price><currency>EUR</currency><value>1499.95</value></price>
+					<price><currency>GBP</currency><value>1699.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="assault-rifle-annual-gunclub-discount">
+			<product>Assault-Rifle</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice>
+					</fixedPrice>
+				</phase>
+				<phase type="DISCOUNT">
+					<duration>
+						<unit>MONTHS</unit>
+						<number>6</number>
+					</duration>
+					<billingPeriod>MONTHLY</billingPeriod>
+					<recurringPrice>
+						<price><currency>USD</currency><value>99.95</value></price>								
+						<price><currency>EUR</currency><value>99.95</value></price>
+						<price><currency>GBP</currency><value>99.95</value></price>
+						</recurringPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>5999.95</value></price>								
+					<price><currency>EUR</currency><value>3499.95</value></price>
+					<price><currency>GBP</currency><value>3999.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="laser-scope-monthly">
+		<product>Laser-Scope</product>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>MONTHLY</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>1999.95</value></price>								
+					<price><currency>EUR</currency><value>1499.95</value></price>
+					<price><currency>GBP</currency><value>1999.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="telescopic-scope-monthly">
+			<product>Telescopic-Scope</product>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>MONTHLY</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>999.95</value></price>								
+					<price><currency>EUR</currency><value>499.95</value></price>
+					<price><currency>GBP</currency><value>999.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="extra-ammo-monthly">
+			<product>Extra-Ammo</product>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>MONTHLY</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>999.95</value></price>								
+					<price><currency>EUR</currency><value>499.95</value></price>
+					<price><currency>GBP</currency><value>999.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+			<plansAllowedInBundle>-1</plansAllowedInBundle> <!-- arbitrary number of these (multipack) -->
+		</plan>
+		<plan name="holster-monthly-regular">
+			<product>Holster</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice>
+					</fixedPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>199.95</value></price>								
+					<price><currency>EUR</currency><value>199.95</value></price>
+					<price><currency>GBP</currency><value>199.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="holster-monthly-special">
+			<product>Holster</product>
+			<initialPhases>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice>
+					</fixedPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>199.95</value></price>								
+					<price><currency>EUR</currency><value>199.95</value></price>
+					<price><currency>GBP</currency><value>199.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="assault-rifle-annual-rescue">
+			<product>Assault-Rifle</product>
+			<initialPhases>
+				<phase type="DISCOUNT">
+					<duration>
+						<unit>YEARS</unit>
+						<number>1</number>
+					</duration>
+					<billingPeriod>ANNUAL</billingPeriod>
+					<recurringPrice>
+						<price><currency>USD</currency><value>5999.95</value></price>								
+						<price><currency>EUR</currency><value>3499.95</value></price>
+						<price><currency>GBP</currency><value>3999.95</value></price>
+					</recurringPrice>
+				</phase>
+			</initialPhases>
+			<finalPhase type="EVERGREEN">
+				<duration>
+					<unit>UNLIMITED</unit>
+				</duration>
+				<billingPeriod>ANNUAL</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>5999.95</value></price>								
+					<price><currency>EUR</currency><value>3499.95</value></price>
+					<price><currency>GBP</currency><value>3999.95</value></price>
+				</recurringPrice>
+			</finalPhase>
+		</plan>
+		<plan name="refurbish-maintenance">
+			<product>Refurbish-Maintenance</product>
+			<finalPhase type="FIXEDTERM">
+				<duration>
+					<unit>MONTHS</unit>
+					<number>12</number>
+				</duration>
+				<billingPeriod>MONTHLY</billingPeriod>
+				<recurringPrice>
+					<price><currency>USD</currency><value>199.95</value></price>								
+					<price><currency>EUR</currency><value>199.95</value></price>
+					<price><currency>GBP</currency><value>199.95</value></price>
+				</recurringPrice>
+				<fixedPrice>
+					<price><currency>USD</currency><value>599.95</value></price>								
+					<price><currency>EUR</currency><value>599.95</value></price>
+					<price><currency>GBP</currency><value>599.95</value></price>
+				</fixedPrice>
+			</finalPhase>
+		</plan>
+	</plans>
+	<priceLists>
+		<defaultPriceList name="DEFAULT"> 
+			<plans>
+				<plan>pistol-monthly</plan>
+				<plan>shotgun-monthly</plan>
+				<plan>assault-rifle-monthly</plan>
+				<plan>pistol-annual</plan>
+				<plan>shotgun-annual</plan>
+				<plan>assault-rifle-annual</plan>
+				<plan>laser-scope-monthly</plan>
+				<plan>telescopic-scope-monthly</plan>
+				<plan>extra-ammo-monthly</plan>
+				<plan>holster-monthly-regular</plan>
+				<plan>refurbish-maintenance</plan>
+			</plans>
+		</defaultPriceList>
+		<childPriceList name="gunclubDiscount">
+			<plans>
+				<plan>pistol-monthly</plan>
+				<plan>shotgun-monthly</plan>
+				<plan>assault-rifle-monthly</plan>
+				<plan>pistol-annual-gunclub-discount</plan>
+				<plan>shotgun-annual-gunclub-discount</plan>
+				<plan>assault-rifle-annual-gunclub-discount</plan>
+				<plan>holster-monthly-special</plan>
+			</plans>
+		</childPriceList>
+		<childPriceList name="rescue">
+			<plans>
+				<plan>assault-rifle-annual-rescue</plan>
+			</plans>
+		</childPriceList>
+	</priceLists>
+
+</catalog>
diff --git a/beatrix/src/test/resources/log4j.xml b/beatrix/src/test/resources/log4j.xml
index 75abc76..ac530a1 100644
--- a/beatrix/src/test/resources/log4j.xml
+++ b/beatrix/src/test/resources/log4j.xml
@@ -29,6 +29,10 @@
         <level value="info"/>
     </logger>
 
+    <logger name="com.ning.billing.util.notificationq">
+        <level value="info"/>
+    </logger>
+
     <root>
         <priority value="info"/>
         <appender-ref ref="stdout"/>
diff --git a/beatrix/src/test/resources/resource.properties b/beatrix/src/test/resources/resource.properties
new file mode 100644
index 0000000..d63334b
--- /dev/null
+++ b/beatrix/src/test/resources/resource.properties
@@ -0,0 +1,7 @@
+killbill.catalog.uri=file:src/test/resources/catalogSample.xml
+killbill.entitlement.dao.claim.time=60000
+killbill.entitlement.dao.ready.max=1
+killbill.entitlement.engine.notifications.sleep=500
+user.timezone=UTC
+
+

catalog/pom.xml 2(+1 -1)

diff --git a/catalog/pom.xml b/catalog/pom.xml
index 1324140..635fac3 100644
--- a/catalog/pom.xml
+++ b/catalog/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.3-SNAPSHOT</version>
+        <version>0.1.5-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-catalog</artifactId>
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultCatalogService.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultCatalogService.java
index 7f88f07..b824617 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultCatalogService.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultCatalogService.java
@@ -28,6 +28,7 @@ import com.ning.billing.lifecycle.LifecycleHandlerType;
 import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
 
 public class DefaultCatalogService implements KillbillService, Provider<Catalog>, CatalogService {
+    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultCatalogService.class);
 
     private static final String CATALOG_SERVICE_NAME = "catalog-service";
 
@@ -54,7 +55,7 @@ public class DefaultCatalogService implements KillbillService, Provider<Catalog>
             	System.out.println("Really really::" + config.getCatalogURI());
             	String url = config.getCatalogURI();
             	catalog = loader.load(url);
-            	
+
                 //catalog = XMLLoader.getObjectFromProperty(config.getCatalogURI(), Catalog.class);
                 isInitialized = true;
             } catch (Exception e) {
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 be3ea55..878497b 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java
@@ -20,6 +20,7 @@ import com.ning.billing.catalog.api.Duration;
 import com.ning.billing.catalog.api.TimeUnit;
 import com.ning.billing.util.config.ValidatingConfig;
 import com.ning.billing.util.config.ValidationErrors;
+import org.joda.time.DateTime;
 
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
@@ -49,7 +50,25 @@ public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> impleme
         return number;
     }
 
-	@Override
+    @Override
+    public DateTime addToDateTime(DateTime dateTime) {
+        if (number < 0) {return null;}
+
+        switch (unit) {
+            case DAYS:
+                return dateTime.plusDays(number);
+            case MONTHS:
+                return dateTime.plusMonths(number);
+            case YEARS:
+                return dateTime.plusYears(number);
+            case UNLIMITED:
+                return dateTime.plusYears(100);
+        }
+
+        return null;
+    }
+
+    @Override
 	public ValidationErrors validate(StandaloneCatalog catalog, ValidationErrors errors) {
 		//TODO MDW - Validation TimeUnit UNLIMITED iff number == -1
 		return errors;
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 6990d3c..1c505f9 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultInternationalPrice.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultInternationalPrice.java
@@ -42,7 +42,7 @@ public class DefaultInternationalPrice extends ValidatingConfig<StandaloneCatalo
 
 
 	/* (non-Javadoc)
-	 * @see com.ning.billing.catalog.IInternationalPrice#getPrices()
+	 * @see com.ning.billing.catalog.InternationalPrice#getPrices()
 	 */
 	@Override
 	public Price[] getPrices() {
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultPlanPhase.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultPlanPhase.java
index 4afe278..6d00162 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultPlanPhase.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultPlanPhase.java
@@ -17,7 +17,13 @@
 package com.ning.billing.catalog;
 
 import com.ning.billing.ErrorCode;
-import com.ning.billing.catalog.api.*;
+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.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.util.config.ValidatingConfig;
 import com.ning.billing.util.config.ValidationError;
 import com.ning.billing.util.config.ValidationErrors;
@@ -38,7 +44,7 @@ public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implem
     private DefaultDuration duration;
     
     @XmlElement(required=true)
-    private BillingPeriod billingPeriod = BillingPeriod.NO_BILLING_PERIOD;
+    private BillingPeriod billingPeriod;
 
 	@XmlElement(required=false)
 	private DefaultInternationalPrice recurringPrice;
@@ -127,30 +133,31 @@ public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implem
 	public ValidationErrors validate(StandaloneCatalog catalog, ValidationErrors errors) {
 		//Validation: check for nulls
 		if(billingPeriod == null) {
-			errors.add(new ValidationError(String.format("Phase %s of plan %s has a reccurring price but no billing period", type.toString(), plan.getName()), 
+			errors.add(new ValidationError(String.format("Phase %s of plan %s has a recurring price but no billing period", type.toString(), plan.getName()),
 					catalog.getCatalogURI(), DefaultPlanPhase.class, type.toString()));
 		}
-		
+	
 		//Validation: if there is a recurring price there must be a billing period
-		if(recurringPrice != null && (billingPeriod == null || billingPeriod ==BillingPeriod.NO_BILLING_PERIOD)) {
-			errors.add(new ValidationError(String.format("Phase %s of plan %s has a reccurring price but no billing period", type.toString(), plan.getName()), 
+		if((recurringPrice != null) && (billingPeriod == null || billingPeriod == BillingPeriod.NO_BILLING_PERIOD)) {
+			errors.add(new ValidationError(String.format("Phase %s of plan %s has a recurring price but no billing period", type.toString(), plan.getName()),
 					catalog.getCatalogURI(), DefaultPlanPhase.class, type.toString()));
 		}
-		//Validation: if there is no reccuring price there should be no billing period
-		if(recurringPrice == null && billingPeriod != BillingPeriod.NO_BILLING_PERIOD) {
-			errors.add(new ValidationError(String.format("Phase %s of plan %s has no reccurring price but does have a billing period. The billing period should be set to '%s'", 
+
+		//Validation: if there is no recurring price there should be no billing period
+		if((recurringPrice == null) && billingPeriod != BillingPeriod.NO_BILLING_PERIOD) {
+			errors.add(new ValidationError(String.format("Phase %s of plan %s has no recurring price but does have a billing period. The billing period should be set to '%s'",
 					type.toString(), plan.getName(), BillingPeriod.NO_BILLING_PERIOD), 
 					catalog.getCatalogURI(), DefaultPlanPhase.class, type.toString()));
 		}
 		
-		//Validation: there must be at least one of reccuringPrice or fixedPrice
-		if(recurringPrice == null && fixedPrice == null) {
-			errors.add(new ValidationError(String.format("Phase %s of plan %s has neither a reccurring price or a fixed price.", 
+		//Validation: there must be at least one of recurringPrice or fixedPrice
+		if((recurringPrice == null) && fixedPrice == null) {
+			errors.add(new ValidationError(String.format("Phase %s of plan %s has neither a recurring price or a fixed price.",
 					type.toString(), plan.getName()), 
 					catalog.getCatalogURI(), DefaultPlanPhase.class, type.toString()));
 		}
-		return errors;
-
+		//TODO : if there BP is set to NO_BILLING_PERIOD there must be a recurring price
+        return errors;
 	}
 	
 	@Override
@@ -164,7 +171,7 @@ public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implem
 		return this;
 	}
 
-	protected DefaultPlanPhase setReccuringPrice(DefaultInternationalPrice price) {
+	protected DefaultPlanPhase setRecurringPrice(DefaultInternationalPrice price) {
 		this.recurringPrice = price;
 		return this;
 	}
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultPrice.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultPrice.java
index d9d26a6..9b5f00b 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultPrice.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultPrice.java
@@ -35,6 +35,16 @@ public class DefaultPrice extends ValidatingConfig<StandaloneCatalog> implements
 	@XmlElement(required=true,nillable=true)
 	private BigDecimal value;
 
+    public DefaultPrice() {
+        // for serialization support
+    }
+
+    public DefaultPrice(final BigDecimal value, final Currency currency) {
+        // for sanity support
+        this.value = value;
+        this.currency = currency;
+    }
+
 	/* (non-Javadoc)
 	 * @see com.ning.billing.catalog.IPrice#getCurrency()
 	 */
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceList.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceList.java
index 48de5d4..aba447d 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceList.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceList.java
@@ -23,7 +23,13 @@ import com.ning.billing.util.config.ValidatingConfig;
 import com.ning.billing.util.config.ValidationError;
 import com.ning.billing.util.config.ValidationErrors;
 
-import javax.xml.bind.annotation.*;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlID;
+import javax.xml.bind.annotation.XmlIDREF;
 
 @XmlAccessorType(XmlAccessType.NONE)
 public class DefaultPriceList extends ValidatingConfig<StandaloneCatalog> implements PriceList  {
@@ -36,8 +42,7 @@ public class DefaultPriceList extends ValidatingConfig<StandaloneCatalog> implem
 	private Boolean retired = false;
 	
 	@XmlElementWrapper(name="plans", required=true)
-	@XmlElement(name="plan", required=true)
-	@XmlIDREF
+	@XmlIDREF @XmlElement(name="plan", required=true)
     private DefaultPlan[] plans;
 	
 	public DefaultPriceList(){}
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultProduct.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultProduct.java
index bf8cfd4..ff3868c 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultProduct.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultProduct.java
@@ -21,15 +21,20 @@ import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.util.config.ValidatingConfig;
 import com.ning.billing.util.config.ValidationErrors;
 
-import javax.xml.bind.annotation.*;
-
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlID;
+import javax.xml.bind.annotation.XmlIDREF;
 import java.net.URI;
 
 @XmlAccessorType(XmlAccessType.NONE)
 public class DefaultProduct extends ValidatingConfig<StandaloneCatalog> implements Product {
 	private static final DefaultProduct[] EMPTY_PRODUCT_LIST = new DefaultProduct[0];
 	
-	@XmlAttribute (required=true)
+	@XmlAttribute(required=true)
 	@XmlID
     private String name;
 
@@ -42,7 +47,7 @@ public class DefaultProduct extends ValidatingConfig<StandaloneCatalog> implemen
 	@XmlElementWrapper(name="included", required=false)
 	@XmlIDREF @XmlElement(name="addonProduct", required=true)
     private DefaultProduct[] included = EMPTY_PRODUCT_LIST;
-	
+
 	@XmlElementWrapper(name="available", required=false)
 	@XmlIDREF @XmlElement(name="addonProduct", required=true)
     private DefaultProduct[] available = EMPTY_PRODUCT_LIST;
diff --git a/catalog/src/main/java/com/ning/billing/catalog/rules/CaseChange.java b/catalog/src/main/java/com/ning/billing/catalog/rules/CaseChange.java
index f101ef1..963dc2f 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/rules/CaseChange.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/rules/CaseChange.java
@@ -19,7 +19,12 @@ package com.ning.billing.catalog.rules;
 import com.ning.billing.catalog.DefaultPriceList;
 import com.ning.billing.catalog.DefaultProduct;
 import com.ning.billing.catalog.StandaloneCatalog;
-import com.ning.billing.catalog.api.*;
+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.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PlanSpecifier;
+import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.util.config.ValidatingConfig;
 import com.ning.billing.util.config.ValidationErrors;
 
diff --git a/catalog/src/test/java/com/ning/billing/catalog/MockCatalog.java b/catalog/src/test/java/com/ning/billing/catalog/MockCatalog.java
index 765d4bd..5fc51b8 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/MockCatalog.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/MockCatalog.java
@@ -19,7 +19,11 @@ package com.ning.billing.catalog;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.ProductCategory;
-import com.ning.billing.catalog.rules.*;
+import com.ning.billing.catalog.rules.CaseCancelPolicy;
+import com.ning.billing.catalog.rules.CaseChangePlanAlignment;
+import com.ning.billing.catalog.rules.CaseChangePlanPolicy;
+import com.ning.billing.catalog.rules.CaseCreateAlignment;
+import com.ning.billing.catalog.rules.PlanRules;
 
 import java.util.Date;
 
@@ -60,7 +64,7 @@ public class MockCatalog extends StandaloneCatalog {
 		DefaultProduct[] products = getCurrentProducts();
 		DefaultPlan[] plans = new DefaultPlan[products.length];
 		for(int i = 0; i < products.length; i++) {
-			DefaultPlanPhase phase = new DefaultPlanPhase().setPhaseType(PhaseType.EVERGREEN).setBillingPeriod(BillingPeriod.MONTHLY).setReccuringPrice(new DefaultInternationalPrice());
+			DefaultPlanPhase phase = new DefaultPlanPhase().setPhaseType(PhaseType.EVERGREEN).setBillingPeriod(BillingPeriod.MONTHLY).setRecurringPrice(new DefaultInternationalPrice());
 			plans[i] = new MockPlan().setName(products[i].getName().toLowerCase() + "-plan").setProduct(products[i]).setFinalPhase(phase);
 		}
 		setPlans(plans);
diff --git a/catalog/src/test/java/com/ning/billing/catalog/MockInternationalPrice.java b/catalog/src/test/java/com/ning/billing/catalog/MockInternationalPrice.java
index 773c58b..cc3a679 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/MockInternationalPrice.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/MockInternationalPrice.java
@@ -19,17 +19,16 @@ package com.ning.billing.catalog;
 import com.ning.billing.catalog.api.Currency;
 
 import java.math.BigDecimal;
-import java.util.Date;
 
 public class MockInternationalPrice extends DefaultInternationalPrice {
-	
-	MockInternationalPrice() {
+
+	public MockInternationalPrice() {
 		setPrices(new DefaultPrice[] {
-			new DefaultPrice().setCurrency(Currency.USD).setValue(new BigDecimal(1))	
+			new DefaultPrice().setCurrency(Currency.USD).setValue(new BigDecimal(1))
 		});
 	}
-	
-	MockInternationalPrice(DefaultPrice... price) {
+
+	public MockInternationalPrice(DefaultPrice... price) {
 		setPrices(price);
 	}
 
diff --git a/catalog/src/test/java/com/ning/billing/catalog/MockPlan.java b/catalog/src/test/java/com/ning/billing/catalog/MockPlan.java
index b9e9afe..53c73fe 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/MockPlan.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/MockPlan.java
@@ -34,6 +34,14 @@ public class MockPlan extends DefaultPlan {
 		setPlansAllowedInBundle(1);
 	}
 
+    public MockPlan(String planName) {
+		setName(planName);
+		setProduct(new MockProduct());
+		setFinalPhase(new MockPlanPhase(this));
+		setInitialPhases(null);
+		setPlansAllowedInBundle(1);
+	}
+
 	public MockPlan(MockPlanPhase mockPlanPhase) {
 		setName("test-plan");
 		setProduct(new MockProduct());
diff --git a/catalog/src/test/java/com/ning/billing/catalog/MockPlanPhase.java b/catalog/src/test/java/com/ning/billing/catalog/MockPlanPhase.java
index fb244eb..d4ae5ff 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/MockPlanPhase.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/MockPlanPhase.java
@@ -18,8 +18,11 @@ package com.ning.billing.catalog;
 
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.TimeUnit;
 
+import javax.annotation.Nullable;
+
 public class MockPlanPhase extends DefaultPlanPhase {
 
     public MockPlanPhase(
@@ -31,16 +34,34 @@ public class MockPlanPhase extends DefaultPlanPhase {
 		setBillingPeriod(billingPeriod);
 		setPhaseType(type);
 		setDuration(duration);
-		setReccuringPrice(recurringPrice);
+		setRecurringPrice(recurringPrice);
 		setFixedPrice(fixedPrice);
 	}
     
     public MockPlanPhase() {
-		setBillingPeriod(BillingPeriod.MONTHLY);
-		setPhaseType(PhaseType.EVERGREEN);
+        this(new MockInternationalPrice(), null);
+	}
+
+    public MockPlanPhase(@Nullable MockInternationalPrice recurringPrice,
+                         @Nullable MockInternationalPrice fixedPrice) {
+        this(recurringPrice, fixedPrice, BillingPeriod.MONTHLY);
+	}
+
+    public MockPlanPhase(@Nullable MockInternationalPrice recurringPrice,
+                         @Nullable MockInternationalPrice fixedPrice,
+                         BillingPeriod billingPeriod) {
+		this(recurringPrice, fixedPrice, billingPeriod, PhaseType.EVERGREEN);
+	}
+
+    public MockPlanPhase(@Nullable MockInternationalPrice recurringPrice,
+                         @Nullable MockInternationalPrice fixedPrice,
+                         BillingPeriod billingPeriod,
+                         PhaseType phaseType) {
+		setBillingPeriod(billingPeriod);
+		setPhaseType(phaseType);
 		setDuration(new DefaultDuration().setNumber(-1).setUnit(TimeUnit.UNLIMITED));
-		setReccuringPrice(new MockInternationalPrice());
-		setFixedPrice(null);
+		setRecurringPrice(recurringPrice);
+		setFixedPrice(fixedPrice);
 		setPlan(new MockPlan(this));
 	}
 
@@ -48,10 +69,17 @@ public class MockPlanPhase extends DefaultPlanPhase {
 		setBillingPeriod(BillingPeriod.MONTHLY);
 		setPhaseType(PhaseType.EVERGREEN);
 		setDuration(new DefaultDuration().setNumber(-1).setUnit(TimeUnit.UNLIMITED));
-		setReccuringPrice(new MockInternationalPrice());
+		setRecurringPrice(new MockInternationalPrice());
 		setFixedPrice(null);
 		setPlan(mockPlan);
 	}
 
-	
+    public MockPlanPhase(Plan plan, PhaseType phaseType) {
+		setBillingPeriod(BillingPeriod.MONTHLY);
+		setPhaseType(phaseType);
+		setDuration(new DefaultDuration().setNumber(-1).setUnit(TimeUnit.UNLIMITED));
+		setRecurringPrice(new MockInternationalPrice());
+		setFixedPrice(null);
+		setPlan(plan);
+	}
 }
diff --git a/catalog/src/test/java/com/ning/billing/catalog/rules/TestCaseChange.java b/catalog/src/test/java/com/ning/billing/catalog/rules/TestCaseChange.java
index f684503..ef1eb17 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/rules/TestCaseChange.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/rules/TestCaseChange.java
@@ -21,9 +21,14 @@ import com.ning.billing.catalog.DefaultPriceList;
 import com.ning.billing.catalog.DefaultProduct;
 import com.ning.billing.catalog.MockCatalog;
 import com.ning.billing.catalog.StandaloneCatalog;
-import com.ning.billing.catalog.api.*;
-import com.ning.billing.catalog.rules.TestCase.CaseResult;
 
+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.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PlanSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
@@ -39,10 +44,10 @@ public class TestCaseChange {
 		private Result result;
 
 		public CaseChangeResult(DefaultProduct from, DefaultProduct to, 
-				ProductCategory fromProductCategory, ProductCategory toProductCategory, 
-				BillingPeriod fromBP, BillingPeriod toBP, 
+				ProductCategory fromProductCategory, ProductCategory toProductCategory,
+				BillingPeriod fromBP, BillingPeriod toBP,
 				DefaultPriceList fromPriceList, DefaultPriceList toPriceList,
-				PhaseType fromType, 
+				PhaseType fromType,
 				Result result) {
 			setFromProduct(from);
 			setToProduct(to);
@@ -63,7 +68,7 @@ public class TestCaseChange {
 		}
 	}
 	@Test(enabled=true)
-	public void testBasic() throws CatalogApiException{
+	public void testBasic() throws CatalogApiException {
 		MockCatalog cat = new MockCatalog();
 
 		DefaultProduct product1 = cat.getCurrentProducts()[0];
@@ -1043,8 +1048,8 @@ public class TestCaseChange {
 				String fromPriceListName, String toPriceListName,
 				PhaseType phaseType, StandaloneCatalog cat){
 	        try{
-	        	cr.getResult(new PlanPhaseSpecifier(fromProductName, fromProductCategory, fromBp, fromPriceListName, phaseType), 
-						new PlanSpecifier(toProductName, toProductCategory, toBp, toPriceListName),cat);	
+	        	cr.getResult(new PlanPhaseSpecifier(fromProductName, fromProductCategory, fromBp, fromPriceListName, phaseType),
+						new PlanSpecifier(toProductName, toProductCategory, toBp, toPriceListName),cat);
 	        	Assert.fail("Expecting an exception");
 	        } catch (CatalogApiException e) {
 	        	Assert.assertEquals(e.getCode(), ErrorCode.CAT_PRICE_LIST_NOT_FOUND.getCode());
diff --git a/catalog/src/test/java/com/ning/billing/catalog/rules/TestCasePhase.java b/catalog/src/test/java/com/ning/billing/catalog/rules/TestCasePhase.java
index 02f7ab5..1b28da7 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/rules/TestCasePhase.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/rules/TestCasePhase.java
@@ -21,7 +21,11 @@ import com.ning.billing.catalog.DefaultPriceList;
 import com.ning.billing.catalog.DefaultProduct;
 import com.ning.billing.catalog.MockCatalog;
 import com.ning.billing.catalog.StandaloneCatalog;
-import com.ning.billing.catalog.api.*;
+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.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.ProductCategory;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
@@ -195,7 +199,7 @@ public class TestCasePhase {
 	}
 	
 	@Test(enabled=true)
-	public void testOrder() throws CatalogApiException{
+	public void testOrder() throws CatalogApiException {
 		MockCatalog cat = new MockCatalog();
 
 		DefaultProduct product = cat.getCurrentProducts()[0];
diff --git a/catalog/src/test/java/com/ning/billing/catalog/rules/TestPlanRules.java b/catalog/src/test/java/com/ning/billing/catalog/rules/TestPlanRules.java
index 7ac9cc0..e9617ea 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/rules/TestPlanRules.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/rules/TestPlanRules.java
@@ -19,8 +19,17 @@ package com.ning.billing.catalog.rules;
 import com.ning.billing.catalog.DefaultPriceList;
 import com.ning.billing.catalog.DefaultProduct;
 import com.ning.billing.catalog.MockCatalog;
-import com.ning.billing.catalog.api.*;
 
+import com.ning.billing.catalog.api.ActionPolicy;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.IllegalPlanChange;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.PlanAlignmentChange;
+import com.ning.billing.catalog.api.PlanChangeResult;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PlanSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
diff --git a/catalog/src/test/java/com/ning/billing/catalog/TestPlanPhase.java b/catalog/src/test/java/com/ning/billing/catalog/TestPlanPhase.java
index d778c4b..9b07bfe 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/TestPlanPhase.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/TestPlanPhase.java
@@ -32,17 +32,17 @@ public class TestPlanPhase {
 	public void testValidation() {
 		log.info("Testing Plan Phase Validation");
 		
-		DefaultPlanPhase pp = new MockPlanPhase().setBillCycleDuration(BillingPeriod.MONTHLY).setReccuringPrice(null).setFixedPrice(new DefaultInternationalPrice());
+		DefaultPlanPhase pp = new MockPlanPhase().setBillCycleDuration(BillingPeriod.MONTHLY).setRecurringPrice(null).setFixedPrice(new DefaultInternationalPrice());
 		ValidationErrors errors = pp.validate(new MockCatalog(), new ValidationErrors());
 		errors.log(log);
 		Assert.assertEquals(errors.size(), 1);
 
-		pp = new MockPlanPhase().setBillCycleDuration(BillingPeriod.NO_BILLING_PERIOD).setReccuringPrice(new MockInternationalPrice());
+		pp = new MockPlanPhase().setBillCycleDuration(BillingPeriod.NO_BILLING_PERIOD).setRecurringPrice(new MockInternationalPrice());
 		errors = pp.validate(new MockCatalog(), new ValidationErrors());
 		errors.log(log);
 		Assert.assertEquals(errors.size(), 1);
 
-		pp = new MockPlanPhase().setReccuringPrice(null).setFixedPrice(null).setBillCycleDuration(BillingPeriod.NO_BILLING_PERIOD);
+		pp = new MockPlanPhase().setRecurringPrice(null).setFixedPrice(null).setBillCycleDuration(BillingPeriod.NO_BILLING_PERIOD);
 		errors = pp.validate(new MockCatalog(), new ValidationErrors());
 		errors.log(log);
 		Assert.assertEquals(errors.size(), 1);
diff --git a/entitlement/pom.xml b/entitlement/pom.xml
index 4ed28e9..83388f8 100644
--- a/entitlement/pom.xml
+++ b/entitlement/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.3-SNAPSHOT</version>
+        <version>0.1.5-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-entitlement</artifactId>
@@ -48,6 +48,12 @@
         <dependency>
             <groupId>com.ning.billing</groupId>
             <artifactId>killbill-catalog</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-catalog</artifactId>
             <scope>test</scope>
         </dependency>
         <dependency>
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 a228a37..a5199a9 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
@@ -16,16 +16,6 @@ w * Copyright 2010-2011 Ning, Inc.
 
 package com.ning.billing.entitlement.api.billing;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.SortedSet;
-import java.util.TreeSet;
-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;
@@ -42,18 +32,32 @@ import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 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.entitlement.api.user.SubscriptionTransition;
 import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import com.ning.billing.entitlement.engine.dao.SubscriptionSqlDao;
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.UUID;
+
 
 public class DefaultEntitlementBillingApi implements EntitlementBillingApi {
-	private Logger log = LoggerFactory.getLogger(DefaultEntitlementBillingApi.class);
-	
+	private static final Logger log = LoggerFactory.getLogger(DefaultEntitlementBillingApi.class);
+
     private final EntitlementDao dao;
     private final AccountUserApi accountApi;
     private final CatalogService catalogService;
 
     @Inject
-    public DefaultEntitlementBillingApi(EntitlementDao dao, AccountUserApi accountApi, CatalogService catalogService) {
+    public DefaultEntitlementBillingApi(final EntitlementDao dao, final AccountUserApi accountApi, final CatalogService catalogService) {
         super();
         this.dao = dao;
         this.accountApi = accountApi;
@@ -62,48 +66,60 @@ public class DefaultEntitlementBillingApi implements EntitlementBillingApi {
 
     @Override
     public SortedSet<BillingEvent> getBillingEventsForAccount(
-            UUID accountId) {
-        
+            final UUID accountId) {
+
         List<SubscriptionBundle> bundles = dao.getSubscriptionBundleForAccount(accountId);
         List<Subscription> subscriptions = new ArrayList<Subscription>();
-        for (SubscriptionBundle bundle: bundles) {
+        for (final SubscriptionBundle bundle: bundles) {
             subscriptions.addAll(dao.getSubscriptions(bundle.getId()));
         }
 
-        SortedSet<BillingEvent> result = new TreeSet<BillingEvent>();        
-        for (Subscription subscription: subscriptions) {
-        	for (SubscriptionTransition transition : subscription.getAllTransitions()) {
+        SortedSet<BillingEvent> result = new TreeSet<BillingEvent>();
+        for (final Subscription subscription: subscriptions) {
+        	for (final SubscriptionTransition transition : subscription.getAllTransitions()) {
         		try {
-        			result.add(new DefaultBillingEvent(transition, subscription, calculateBCD(transition, accountId)));
+                    BillingEvent event = new DefaultBillingEvent(transition, subscription, calculateBCD(transition, accountId));
+        			result.add(event);
         		} catch (CatalogApiException e) {
-        			log.error("Failing to identify catalog components while creating BillingEvent from transition: " + 
+        			log.error("Failing to identify catalog components while creating BillingEvent from transition: " +
         					transition.getId().toString(), e);
+                } catch (Exception e) {
+                    log.warn("Failed while getting BillingEvent", e);
         		}
         	}
         }
         return result;
     }
-    
-    private int calculateBCD(SubscriptionTransition transition, UUID accountId) throws CatalogApiException {
+
+    @Override
+    public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
+        return dao.getAccountIdFromSubscriptionId(subscriptionId);
+    }
+
+    private int calculateBCD(final SubscriptionTransition transition, final UUID accountId) throws CatalogApiException {
     	Catalog catalog = catalogService.getFullCatalog();
-    	Plan plan = transition.getNextPlan();
+    	Plan plan =  (transition.getTransitionType() != SubscriptionTransitionType.CANCEL) ?
+    	        transition.getNextPlan() : transition.getPreviousPlan();
     	Product product = plan.getProduct();
-    	PlanPhase phase = transition.getNextPhase();
-    	
+    	PlanPhase phase = (transition.getTransitionType() != SubscriptionTransitionType.CANCEL) ?
+    	        transition.getNextPhase() : transition.getPreviousPhase();
+
     	BillingAlignment alignment = catalog.billingAlignment(
-    			new PlanPhaseSpecifier(product.getName(), 
-    					product.getCategory(), 
-    					phase.getBillingPeriod(), 
-    					transition.getNextPriceList(), 
-    					phase.getPhaseType()), 
+    			new PlanPhaseSpecifier(product.getName(),
+    					product.getCategory(),
+    					phase.getBillingPeriod(),
+    					transition.getNextPriceList(),
+    					phase.getPhaseType()),
     					transition.getRequestedTransitionTime());
     	int result = 0;
-    	Account account = accountApi.getAccountById(accountId);
+
+        Account account = accountApi.getAccountById(accountId);
+
     	switch (alignment) {
-    		case ACCOUNT : 
+    		case ACCOUNT :
     			result = account.getBillCycleDay();
     		break;
-    		case BUNDLE : 
+    		case BUNDLE :
     			SubscriptionBundle bundle = dao.getSubscriptionBundleFromId(transition.getBundleId());
     			//TODO result = bundle.getStartDate().toDateTime(account.getTimeZone()).getDayOfMonth();
     			result = bundle.getStartDate().getDayOfMonth();
@@ -118,20 +134,32 @@ public class DefaultEntitlementBillingApi implements EntitlementBillingApi {
     		throw new CatalogApiException(ErrorCode.CAT_INVALID_BILLING_ALIGNMENT, alignment.toString());
     	}
     	return result;
-    		
+
     }
-    
 
     @Override
-    public void setChargedThroughDate(UUID subscriptionId, DateTime ctd) {
+    public void setChargedThroughDate(final UUID subscriptionId, final DateTime ctd) {
         SubscriptionData subscription = (SubscriptionData) dao.getSubscriptionFromId(subscriptionId);
-        if (subscription == null) {
-            new EntitlementBillingApiException(String.format("Unknown subscription %s", subscriptionId));
-        }
 
         SubscriptionBuilder builder = new SubscriptionBuilder(subscription)
             .setChargedThroughDate(ctd)
             .setPaidThroughDate(subscription.getPaidThroughDate());
+
         dao.updateSubscription(new SubscriptionData(builder));
     }
+
+    @Override
+    public void setChargedThroughDateFromTransaction(final Transmogrifier transactionalDao, final UUID subscriptionId, final DateTime ctd) {
+        SubscriptionSqlDao subscriptionSqlDao = transactionalDao.become(SubscriptionSqlDao.class);
+        SubscriptionData subscription = (SubscriptionData) subscriptionSqlDao.getSubscriptionFromId(subscriptionId.toString());
+
+        if (subscription == null) {
+            log.warn("Subscription not found when setting CTD.");
+        } else {
+            Date paidThroughDate = (subscription.getPaidThroughDate() == null) ? null : subscription.getPaidThroughDate().toDate();
+
+            subscriptionSqlDao.updateSubscription(subscriptionId.toString(), subscription.getActiveVersion(),
+                                                  ctd.toDate(), paidThroughDate);
+        }
+    }
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/DefaultEntitlementMigrationApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/DefaultEntitlementMigrationApi.java
index d8a0100..05a063d 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/DefaultEntitlementMigrationApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/DefaultEntitlementMigrationApi.java
@@ -111,7 +111,7 @@ public class DefaultEntitlementMigrationApi implements EntitlementMigrationApi {
                     // Not implemented yet
                     break;
                 case STANDALONE:
-                    // Not implemented yet
+                    data = createStandaloneSubscriptionMigrationData(bundleData.getId(), curSub.getCategory(), curSub.getSubscriptionCases(), now);
                     break;
                 default:
                     throw new EntitlementMigrationApiException(String.format("Unkown product type ", curSub.getCategory()));
@@ -144,6 +144,22 @@ public class DefaultEntitlementMigrationApi implements EntitlementMigrationApi {
         return new SubscriptionMigrationData(subscriptionData, toEvents(subscriptionData, now, events));
     }
 
+    private SubscriptionMigrationData createStandaloneSubscriptionMigrationData(UUID bundleId, ProductCategory productCategory,
+            EntitlementSubscriptionMigrationCase [] input, DateTime now)
+    throws EntitlementMigrationApiException {
+        TimedMigration [] events = migrationAligner.getEventsMigration(input, now);
+        DateTime migrationStartDate= events[0].getEventTime();
+        List<EntitlementEvent> emptyEvents =  Collections.emptyList();
+        SubscriptionData subscriptionData = factory.createSubscription(new SubscriptionBuilder()
+            .setId(UUID.randomUUID())
+            .setBundleId(bundleId)
+            .setCategory(productCategory)
+            .setBundleStartDate(migrationStartDate)
+            .setStartDate(migrationStartDate),
+            emptyEvents);
+        return new SubscriptionMigrationData(subscriptionData, toEvents(subscriptionData, now, events));
+    }
+
     private List<EntitlementEvent> toEvents(SubscriptionData subscriptionData, DateTime now, TimedMigration [] migrationEvents) {
 
         List<EntitlementEvent> events = new ArrayList<EntitlementEvent>(migrationEvents.length);
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/test/DefaultEntitlementTestApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/test/DefaultEntitlementTestApi.java
index 19b851d..2843ec1 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/test/DefaultEntitlementTestApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/test/DefaultEntitlementTestApi.java
@@ -19,6 +19,7 @@ package com.ning.billing.entitlement.api.test;
 import com.google.inject.Inject;
 import com.ning.billing.config.EntitlementConfig;
 import com.ning.billing.entitlement.engine.core.Engine;
+import com.ning.billing.entitlement.exceptions.EntitlementError;
 import com.ning.billing.util.notificationq.NotificationQueue;
 import com.ning.billing.util.notificationq.NotificationQueueService;
 import com.ning.billing.util.notificationq.NotificationQueueService.NoSuchNotificationQueue;
@@ -26,6 +27,7 @@ import com.ning.billing.util.notificationq.NotificationQueueService.NoSuchNotifi
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+
 import java.util.UUID;
 
 public class DefaultEntitlementTestApi implements EntitlementTestApi {
@@ -43,11 +45,13 @@ public class DefaultEntitlementTestApi implements EntitlementTestApi {
 
     @Override
     public void doProcessReadyEvents(UUID [] subscriptionsIds, Boolean recursive, Boolean oneEventOnly) {
+        if (recursive || oneEventOnly) {
+            throw new EntitlementError("Not implemented");
+        }
         if (config.isEventProcessingOff()) {
             log.warn("Running event processing loop");
             NotificationQueue queue = getNotificationQueue();
             queue.processReadyNotification();
-
         }
     }
 
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java
index 42d34ad..ed05b02 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java
@@ -89,7 +89,6 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
 
     @Override
     public Subscription createSubscription(UUID bundleId, PlanPhaseSpecifier spec, DateTime requestedDate) throws EntitlementUserApiException {
-
         try {
             String realPriceList = (spec.getPriceListName() == null) ? PriceListSet.DEFAULT_PRICELIST_NAME : spec.getPriceListName();
             DateTime now = clock.getUTCNow();
@@ -102,7 +101,6 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
 
             Plan plan = catalogService.getFullCatalog().findPlan(spec.getProductName(), spec.getBillingPeriod(), realPriceList, requestedDate);
 
-
             PlanPhase phase = (plan.getInitialPhases() != null) ? plan.getInitialPhases()[0] : plan.getFinalPhase();
             if (phase == null) {
                 throw new EntitlementError(String.format("No initial PlanPhase for Product %s, term %s and set %s does not exist in the catalog",
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 272f234..342c9cc 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
@@ -30,7 +30,12 @@ import org.joda.time.DateTime;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
 
 public class SubscriptionData implements Subscription {
 
@@ -99,29 +104,29 @@ public class SubscriptionData implements Subscription {
 
     @Override
     public SubscriptionState getState() {
-        return (transitions == null) ? null : getLatestTranstion().getNextState();
+        return (getLatestTransition() == null) ? null : getLatestTransition().getNextState();
     }
 
     @Override
     public PlanPhase getCurrentPhase() {
-        return (transitions == null) ? null : getLatestTranstion().getNextPhase();
+        return (getLatestTransition() == null) ? null : getLatestTransition().getNextPhase();
     }
 
 
     @Override
     public Plan getCurrentPlan() {
-        return (transitions == null) ? null : getLatestTranstion().getNextPlan();
+        return (getLatestTransition() == null) ? null : getLatestTransition().getNextPlan();
     }
 
     @Override
     public String getCurrentPriceList() {
-        return (transitions == null) ? null : getLatestTranstion().getNextPriceList();
+        return (getLatestTransition() == null) ? null : getLatestTransition().getNextPriceList();
     }
 
 
     @Override
     public DateTime getEndDate() {
-        SubscriptionTransition latestTransition = getLatestTranstion();
+        SubscriptionTransition latestTransition = getLatestTransition();
         if (latestTransition.getNextState() == SubscriptionState.CANCELLED) {
             return latestTransition.getEffectiveTransitionTime();
         }
@@ -195,7 +200,7 @@ public class SubscriptionData implements Subscription {
         return null;
     }
 
-    public SubscriptionTransition getLatestTranstion() {
+    public SubscriptionTransition getLatestTransition() {
 
         if (transitions == null) {
             return null;
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 7bd1124..ce70ba7 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
@@ -18,7 +18,6 @@ package com.ning.billing.entitlement.engine.core;
 
 import java.util.UUID;
 
-
 import org.joda.time.DateTime;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -46,12 +45,12 @@ import com.ning.billing.entitlement.exceptions.EntitlementError;
 import com.ning.billing.lifecycle.LifecycleHandlerType;
 import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
 import com.ning.billing.util.clock.Clock;
-import com.ning.billing.util.eventbus.EventBus;
-import com.ning.billing.util.eventbus.EventBus.EventBusException;
+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.NotificationQueue;
 import com.ning.billing.util.notificationq.NotificationQueueService;
-import com.ning.billing.util.notificationq.NotificationQueueService.NotficationQueueAlreadyExists;
+import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueAlreadyExists;
 import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
 
 public class Engine implements EventListener, EntitlementService {
@@ -59,10 +58,6 @@ public class Engine implements EventListener, EntitlementService {
     public static final String NOTIFICATION_QUEUE_NAME = "subscription-events";
     public static final String ENTITLEMENT_SERVICE_NAME = "entitlement-service";
 
-    private final long MAX_NOTIFICATION_THREAD_WAIT_MS = 10000; // 10 secs
-    private final long NOTIFICATION_THREAD_WAIT_INCREMENT_MS = 1000; // 1 sec
-    private final long NANO_TO_MS = (1000 * 1000);
-
     private final static Logger log = LoggerFactory.getLogger(Engine.class);
 
     private final Clock clock;
@@ -72,19 +67,17 @@ public class Engine implements EventListener, EntitlementService {
     private final EntitlementBillingApi billingApi;
     private final EntitlementTestApi testApi;
     private final EntitlementMigrationApi migrationApi;
-    private final EventBus eventBus;
+    private final Bus eventBus;
     private final EntitlementConfig config;
     private final NotificationQueueService notificationQueueService;
 
-    private boolean startedNotificationThread;
-    private boolean stoppedNotificationThread;
     private NotificationQueue subscritionEventQueue;
 
     @Inject
     public Engine(Clock clock, EntitlementDao dao, PlanAligner planAligner,
             EntitlementConfig config, DefaultEntitlementUserApi userApi,
             DefaultEntitlementBillingApi billingApi, DefaultEntitlementTestApi testApi,
-            DefaultEntitlementMigrationApi migrationApi, EventBus eventBus,
+            DefaultEntitlementMigrationApi migrationApi, Bus eventBus,
             NotificationQueueService notificationQueueService) {
         super();
         this.clock = clock;
@@ -108,8 +101,6 @@ public class Engine implements EventListener, EntitlementService {
     public void initialize() {
 
         try {
-            this.stoppedNotificationThread = false;
-            this.startedNotificationThread = false;
             subscritionEventQueue = notificationQueueService.createNotificationQueue(ENTITLEMENT_SERVICE_NAME,
                     NOTIFICATION_QUEUE_NAME,
                     new NotificationQueueHandler() {
@@ -122,21 +113,6 @@ public class Engine implements EventListener, EntitlementService {
                         processEventReady(event);
                     }
                 }
-
-                @Override
-                public void completedQueueStop() {
-                    synchronized (this) {
-                        stoppedNotificationThread = true;
-                        this.notifyAll();
-                    }
-                }
-                @Override
-                public void completedQueueStart() {
-                    synchronized (this) {
-                        startedNotificationThread = true;
-                        this.notifyAll();
-                    }
-                }
             },
             new NotificationConfig() {
                 @Override
@@ -156,7 +132,7 @@ public class Engine implements EventListener, EntitlementService {
                     return config.getDaoMaxReadyEvents();
                 }
             });
-        } catch (NotficationQueueAlreadyExists e) {
+        } catch (NotificationQueueAlreadyExists e) {
             throw new RuntimeException(e);
         }
     }
@@ -164,16 +140,13 @@ public class Engine implements EventListener, EntitlementService {
     @LifecycleHandlerType(LifecycleLevel.START_SERVICE)
     public void start() {
         subscritionEventQueue.startQueue();
-        waitForNotificationStartCompletion();
     }
 
     @LifecycleHandlerType(LifecycleLevel.STOP_SERVICE)
     public void stop() {
         if (subscritionEventQueue != null) {
             subscritionEventQueue.stopQueue();
-            waitForNotificationStopCompletion();
-        }
-        startedNotificationThread = false;
+         }
     }
 
     @Override
@@ -218,43 +191,6 @@ public class Engine implements EventListener, EntitlementService {
         }
     }
 
-    private void waitForNotificationStartCompletion() {
-        waitForNotificationEventCompletion(true);
-    }
-
-    private void waitForNotificationStopCompletion() {
-        waitForNotificationEventCompletion(false);
-    }
-
-    private void waitForNotificationEventCompletion(boolean startEvent) {
-
-        long ini = System.nanoTime();
-        synchronized(this) {
-            do {
-                if ((startEvent ? startedNotificationThread : stoppedNotificationThread)) {
-                    break;
-                }
-                try {
-                    this.wait(NOTIFICATION_THREAD_WAIT_INCREMENT_MS);
-                } catch (InterruptedException e ) {
-                    Thread.currentThread().interrupt();
-                    throw new EntitlementError(e);
-                }
-            } while (!(startEvent ? startedNotificationThread : stoppedNotificationThread) &&
-                    (System.nanoTime() - ini) / NANO_TO_MS < MAX_NOTIFICATION_THREAD_WAIT_MS);
-
-            if (!(startEvent ? startedNotificationThread : stoppedNotificationThread)) {
-                log.error("Could not {} notification thread in {} msec !!!",
-                        (startEvent ? "start" : "stop"),
-                        MAX_NOTIFICATION_THREAD_WAIT_MS);
-                throw new EntitlementError("Failed to start service!!");
-            }
-            log.info("Notification thread has been {} in {} ms",
-                    (startEvent ? "started" : "stopped"),
-                    (System.nanoTime() - ini) / NANO_TO_MS);
-        }
-    }
-
     private void insertNextPhaseEvent(SubscriptionData subscription) {
         try {
             DateTime now = clock.getUTCNow();
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java
index ea62b84..c9ddf90 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java
@@ -16,7 +16,9 @@
 
 package com.ning.billing.entitlement.engine.dao;
 
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
 import com.ning.billing.entitlement.api.migration.AccountMigrationData;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
@@ -40,6 +42,8 @@ public interface EntitlementDao {
 
     public Subscription getSubscriptionFromId(UUID subscriptionId);
 
+    // Account retrieval
+    public UUID getAccountIdFromSubscriptionId(UUID subscriptionId);
 
     // Subscription retrieval
     public Subscription getBaseSubscription(UUID bundleId);
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java
index 72a71d8..e5f15ed 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java
@@ -21,9 +21,18 @@ import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 import java.util.UUID;
-
+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.Transactional;
+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.ErrorCode;
 import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
 import com.ning.billing.entitlement.api.migration.AccountMigrationData;
 import com.ning.billing.entitlement.api.migration.AccountMigrationData.BundleMigrationData;
 import com.ning.billing.entitlement.api.migration.AccountMigrationData.SubscriptionMigrationData;
@@ -44,14 +53,7 @@ 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;
-
-import org.joda.time.DateTime;
-import org.skife.jdbi.v2.DBI;
-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 sun.jkernel.Bundle;
 
 
 public class EntitlementSqlDao implements EntitlementDao {
@@ -66,7 +68,8 @@ public class EntitlementSqlDao implements EntitlementDao {
     private final NotificationQueueService notificationQueueService;
 
     @Inject
-    public EntitlementSqlDao(DBI dbi, Clock clock, SubscriptionFactory factory, NotificationQueueService notificationQueueService) {
+    public EntitlementSqlDao(final IDBI dbi, final Clock clock, final SubscriptionFactory factory,
+                             final NotificationQueueService notificationQueueService) {
         this.clock = clock;
         this.factory = factory;
         this.subscriptionsDao = dbi.onDemand(SubscriptionSqlDao.class);
@@ -76,18 +79,18 @@ public class EntitlementSqlDao implements EntitlementDao {
     }
 
     @Override
-    public SubscriptionBundle getSubscriptionBundleFromKey(String bundleKey) {
+    public SubscriptionBundle getSubscriptionBundleFromKey(final String bundleKey) {
         return bundlesDao.getBundleFromKey(bundleKey);
     }
 
     @Override
     public List<SubscriptionBundle> getSubscriptionBundleForAccount(
-            UUID accountId) {
+            final UUID accountId) {
         return bundlesDao.getBundleFromAccount(accountId.toString());
     }
 
     @Override
-    public SubscriptionBundle getSubscriptionBundleFromId(UUID bundleId) {
+    public SubscriptionBundle getSubscriptionBundleFromId(final UUID bundleId) {
         return bundlesDao.getBundleFromId(bundleId.toString());
     }
 
@@ -103,11 +106,34 @@ public class EntitlementSqlDao implements EntitlementDao {
     }
 
     @Override
-    public Subscription getSubscriptionFromId(UUID subscriptionId) {
+    public Subscription getSubscriptionFromId(final UUID subscriptionId) {
         return buildSubscription(subscriptionsDao.getSubscriptionFromId(subscriptionId.toString()));
     }
 
     @Override
+    public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
+        Subscription subscription = subscriptionsDao.getSubscriptionFromId(subscriptionId.toString());
+        if (subscription == null) {
+            log.error(String.format(ErrorCode.ENT_INVALID_SUBSCRIPTION_ID.getFormat(), subscriptionId.toString()));
+            return null;
+        }
+
+        UUID bundleId = subscription.getBundleId();
+        if (bundleId == null) {
+            log.error(String.format(ErrorCode.ENT_GET_NO_BUNDLE_FOR_SUBSCRIPTION.getFormat(), subscriptionId.toString()));
+            return null;
+        }
+
+        SubscriptionBundle bundle = bundlesDao.getBundleFromId(bundleId.toString());
+        if (bundle == null) {
+            log.error(String.format(ErrorCode.ENT_GET_INVALID_BUNDLE_ID.getFormat(), bundleId.toString()));
+            return null;
+        }
+
+        return bundle.getAccountId();
+    }
+
+    @Override
     public Subscription getBaseSubscription(final UUID bundleId) {
 
         List<Subscription> subscriptions = subscriptionsDao.getSubscriptionsFromBundleId(bundleId.toString());
@@ -194,9 +220,9 @@ public class EntitlementSqlDao implements EntitlementDao {
 
                 dao.insertSubscription(subscription);
                 // STEPH batch as well
-                EventSqlDao eventsDaoFromSameTranscation = dao.become(EventSqlDao.class);
+                EventSqlDao eventsDaoFromSameTransaction = dao.become(EventSqlDao.class);
                 for (final EntitlementEvent cur : initialEvents) {
-                    eventsDaoFromSameTranscation.insertEvent(cur);
+                    eventsDaoFromSameTransaction.insertEvent(cur);
                     recordFutureNotificationFromTransaction(dao,
                             cur.getEffectiveDate(),
                             new NotificationKey() {
@@ -417,9 +443,9 @@ public class EntitlementSqlDao implements EntitlementDao {
 
     private void recordFutureNotificationFromTransaction(final Transmogrifier transactionalDao, final DateTime effectiveDate, final NotificationKey notificationKey) {
         try {
-            NotificationQueue subscritionEventQueue = notificationQueueService.getNotificationQueue(Engine.ENTITLEMENT_SERVICE_NAME,
+            NotificationQueue subscriptionEventQueue = notificationQueueService.getNotificationQueue(Engine.ENTITLEMENT_SERVICE_NAME,
                 Engine.NOTIFICATION_QUEUE_NAME);
-            subscritionEventQueue.recordFutureNotificationFromTransaction(transactionalDao, effectiveDate, notificationKey);
+            subscriptionEventQueue.recordFutureNotificationFromTransaction(transactionalDao, effectiveDate, notificationKey);
         } catch (NoSuchNotificationQueue e) {
             throw new RuntimeException(e);
         }
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 d2776ad..c27a8f4 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
@@ -40,6 +40,7 @@ import com.ning.billing.entitlement.engine.dao.EntitlementSqlDao;
 
 public class EntitlementModule extends AbstractModule {
 
+
     protected void installConfig() {
         final EntitlementConfig config = new ConfigurationObjectFactory(System.getProperties()).build(EntitlementConfig.class);
         bind(EntitlementConfig.class).toInstance(config);
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java
index 6bee471..39f6c48 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java
@@ -19,7 +19,7 @@ package com.ning.billing.entitlement.api;
 import com.google.common.base.Joiner;
 import com.google.common.eventbus.Subscribe;
 import com.ning.billing.entitlement.api.user.SubscriptionTransition;
-import com.ning.billing.util.eventbus.EventBus;
+import com.ning.billing.util.bus.Bus;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -45,7 +45,7 @@ public class ApiTestListener {
         PHASE
     }
 
-    public ApiTestListener(EventBus eventBus) {
+    public ApiTestListener(Bus eventBus) {
         this.nextExpectedEvent = new Stack<NextEvent>();
         this.completed = false;
     }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadMockEntitlementDao.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadMockEntitlementDao.java
index 68cae5a..4d57dac 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadMockEntitlementDao.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadMockEntitlementDao.java
@@ -16,11 +16,10 @@
 
 package com.ning.billing.entitlement.api.billing;
 
-import java.util.Collection;
 import java.util.List;
 import java.util.UUID;
-
 import com.ning.billing.entitlement.api.migration.AccountMigrationData;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
@@ -32,110 +31,112 @@ class BrainDeadMockEntitlementDao implements EntitlementDao {
 
 	@Override
 	public List<SubscriptionBundle> getSubscriptionBundleForAccount(
-			UUID accountId) {
+			final UUID accountId) {
 		throw new UnsupportedOperationException();
 	}
 
 	@Override
-	public SubscriptionBundle getSubscriptionBundleFromKey(String bundleKey) {
+	public SubscriptionBundle getSubscriptionBundleFromKey(final String bundleKey) {
 		throw new UnsupportedOperationException();
 	}
 
 	@Override
-	public SubscriptionBundle getSubscriptionBundleFromId(UUID bundleId) {
+	public SubscriptionBundle getSubscriptionBundleFromId(final UUID bundleId) {
 		throw new UnsupportedOperationException();
 	}
 
 	@Override
 	public SubscriptionBundle createSubscriptionBundle(
-			SubscriptionBundleData bundle) {
+			final SubscriptionBundleData bundle) {
 		throw new UnsupportedOperationException();
 	}
 
 	@Override
-	public Subscription getSubscriptionFromId(UUID subscriptionId) {
+	public Subscription getSubscriptionFromId(final UUID subscriptionId) {
 		throw new UnsupportedOperationException();
 
 	}
 
-	@Override
-	public Subscription getBaseSubscription(UUID bundleId) {
-		throw new UnsupportedOperationException();
+    @Override
+    public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
+        throw new UnsupportedOperationException();
+    }
 
+    @Override
+	public Subscription getBaseSubscription(final UUID bundleId) {
+		throw new UnsupportedOperationException();
 	}
 
 	@Override
-	public List<Subscription> getSubscriptions(UUID bundleId) {
+	public List<Subscription> getSubscriptions(final UUID bundleId) {
 		throw new UnsupportedOperationException();
-
 	}
 
 	@Override
-	public List<Subscription> getSubscriptionsForKey(String bundleKey) {
+	public List<Subscription> getSubscriptionsForKey(final String bundleKey) {
 		throw new UnsupportedOperationException();
 	}
 
 	@Override
-	public void updateSubscription(SubscriptionData subscription) {
+	public void updateSubscription(final SubscriptionData subscription) {
 		throw new UnsupportedOperationException();
 	}
 
 	@Override
-	public void createNextPhaseEvent(UUID subscriptionId,
-			EntitlementEvent nextPhase) {
+	public void createNextPhaseEvent(final UUID subscriptionId,
+			final EntitlementEvent nextPhase) {
 		throw new UnsupportedOperationException();
 	}
 
 	@Override
 	public List<EntitlementEvent> getEventsForSubscription(
-			UUID subscriptionId) {
+			final UUID subscriptionId) {
 		throw new UnsupportedOperationException();
 	}
 
 	@Override
 	public List<EntitlementEvent> getPendingEventsForSubscription(
-			UUID subscriptionId) {
+			final UUID subscriptionId) {
 		throw new UnsupportedOperationException();
 	}
 
 	@Override
-	public void createSubscription(SubscriptionData subscription,
-			List<EntitlementEvent> initialEvents) {
+	public void createSubscription(final SubscriptionData subscription,
+			final List<EntitlementEvent> initialEvents) {
 		throw new UnsupportedOperationException();
 	}
 
 	@Override
-	public void cancelSubscription(UUID subscriptionId,
-			EntitlementEvent cancelEvent) {
+	public void cancelSubscription(final UUID subscriptionId,
+			final EntitlementEvent cancelEvent) {
 		throw new UnsupportedOperationException();
-
 	}
 
 	@Override
-	public void uncancelSubscription(UUID subscriptionId,
-			List<EntitlementEvent> uncancelEvents) {
+	public void uncancelSubscription(final UUID subscriptionId,
+			final List<EntitlementEvent> uncancelEvents) {
 		throw new UnsupportedOperationException();
 	}
 
 	@Override
-	public void changePlan(UUID subscriptionId,
-			List<EntitlementEvent> changeEvents) {
+	public void changePlan(final UUID subscriptionId,
+			final List<EntitlementEvent> changeEvents) {
 		throw new UnsupportedOperationException();
 	}
 
 	@Override
-	public void migrate(UUID acountId, AccountMigrationData data) {
+	public void migrate(final UUID acountId, final AccountMigrationData data) {
 		throw new UnsupportedOperationException();
 	}
 
 	@Override
-	public void undoMigration(UUID accountId) {
-		throw new UnsupportedOperationException();
+	public void undoMigration(final UUID accountId) {
+        throw new UnsupportedOperationException();
 	}
 
-	@Override
-	public EntitlementEvent getEventById(UUID eventId) {
-		throw new UnsupportedOperationException();
-	}
-	
+
+    @Override
+    public EntitlementEvent getEventById(final UUID eventId) {
+        throw new UnsupportedOperationException();
+    }
 }
\ No newline at end of file
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
new file mode 100644
index 0000000..e55ba79
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadSubscription.java
@@ -0,0 +1,144 @@
+/*
+ * 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.entitlement.api.billing;
+
+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();
+		
+	}
+
+}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultBillingEvent.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultBillingEvent.java
new file mode 100644
index 0000000..100d184
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultBillingEvent.java
@@ -0,0 +1,154 @@
+/*
+ * 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.entitlement.api.billing;
+
+import java.math.BigDecimal;
+import java.util.Iterator;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.DefaultPrice;
+import com.ning.billing.catalog.MockInternationalPrice;
+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;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
+
+public class TestDefaultBillingEvent {
+	public static final UUID ID_ZERO = new UUID(0L,0L);
+	public static final UUID ID_ONE = new UUID(0L,1L);
+	public static final UUID ID_TWO = new UUID(0L,2L);
+
+	@Test(groups={"fast"})
+	public void testEventOrderingSubscription() {
+	
+		BillingEvent event0 = createEvent(subscription(ID_ZERO), new DateTime("2012-01-31T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
+		BillingEvent event1 = createEvent(subscription(ID_ONE), new DateTime("2012-01-31T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
+		BillingEvent event2 = createEvent(subscription(ID_TWO), new DateTime("2012-01-31T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
+		
+		SortedSet<BillingEvent> set = new TreeSet<BillingEvent>();
+		set.add(event2);
+		set.add(event1);
+		set.add(event0);
+		
+		Iterator<BillingEvent> it = set.iterator();
+		
+		Assert.assertEquals(event0, it.next());
+		Assert.assertEquals(event1, it.next());
+		Assert.assertEquals(event2, it.next());
+	}
+	
+	@Test(groups={"fast"})
+	public void testEventOrderingDate() {
+	
+		BillingEvent event0 = createEvent(subscription(ID_ZERO), new DateTime("2012-01-01T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
+		BillingEvent event1 = createEvent(subscription(ID_ZERO), new DateTime("2012-02-01T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
+		BillingEvent event2 = createEvent(subscription(ID_ZERO), new DateTime("2012-03-01T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
+		
+		SortedSet<BillingEvent> set = new TreeSet<BillingEvent>();
+		set.add(event2);
+		set.add(event1);
+		set.add(event0);
+		
+		Iterator<BillingEvent> it = set.iterator();
+		
+		Assert.assertEquals(event0, it.next());
+		Assert.assertEquals(event1, it.next());
+		Assert.assertEquals(event2, it.next());
+	}
+	
+	@Test(groups={"fast"})
+	public void testEventOrderingType() {
+	
+		BillingEvent event0 = createEvent(subscription(ID_ZERO), new DateTime("2012-01-01T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
+		BillingEvent event1 = createEvent(subscription(ID_ZERO), new DateTime("2012-01-01T00:02:04.000Z"), SubscriptionTransitionType.CHANGE);
+		BillingEvent event2 = createEvent(subscription(ID_ZERO), new DateTime("2012-01-01T00:02:04.000Z"), SubscriptionTransitionType.CANCEL);
+		
+		SortedSet<BillingEvent> set = new TreeSet<BillingEvent>();
+		set.add(event2);
+		set.add(event1);
+		set.add(event0);
+		
+		Iterator<BillingEvent> it = set.iterator();
+		
+		Assert.assertEquals(event0, it.next());
+		Assert.assertEquals(event1, it.next());
+		Assert.assertEquals(event2, it.next());
+	}
+	
+	@Test(groups={"fast"})
+	public void testEventOrderingMix() {
+	
+		BillingEvent event0 = createEvent(subscription(ID_ZERO), new DateTime("2012-01-01T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
+		BillingEvent event1 = createEvent(subscription(ID_ZERO), new DateTime("2012-01-02T00:02:04.000Z"), SubscriptionTransitionType.CHANGE);
+		BillingEvent event2 = createEvent(subscription(ID_ONE), new DateTime("2012-01-01T00:02:04.000Z"), SubscriptionTransitionType.CANCEL);
+		
+		SortedSet<BillingEvent> set = new TreeSet<BillingEvent>();
+		set.add(event2);
+		set.add(event1);
+		set.add(event0);
+		
+		Iterator<BillingEvent> it = set.iterator();
+		
+		Assert.assertEquals(event0, it.next());
+		Assert.assertEquals(event1, it.next());
+		Assert.assertEquals(event2, it.next());
+	}
+
+	
+	private BillingEvent createEvent(Subscription sub, DateTime effectiveDate, SubscriptionTransitionType type) {
+		InternationalPrice zeroPrice = new MockInternationalPrice(new DefaultPrice(BigDecimal.ZERO, Currency.USD));
+		int billCycleDay = 1;
+
+		Plan shotgun = new MockPlan();
+		PlanPhase shotgunMonthly = createMockMonthlyPlanPhase(null, BigDecimal.ZERO, PhaseType.TRIAL);
+		
+		return new DefaultBillingEvent(sub , effectiveDate,
+				shotgun, shotgunMonthly,
+				zeroPrice, null, BillingPeriod.NO_BILLING_PERIOD, billCycleDay,
+				BillingModeType.IN_ADVANCE, "Test Event 1", type);
+	}
+
+	private MockPlanPhase createMockMonthlyPlanPhase(@Nullable final BigDecimal recurringRate,
+			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 Subscription subscription(final UUID id) {
+		return new BrainDeadSubscription() {
+			public UUID getId() {
+				return id;
+			}
+		};
+	}
+
+}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java
index 2de7872..91ddc91 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java
@@ -42,6 +42,7 @@ import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.glue.CatalogModule;
 import com.ning.billing.entitlement.api.TestApiBase;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
@@ -57,6 +58,8 @@ import com.ning.billing.lifecycle.KillbillService.ServiceException;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.glue.ClockModule;
 
+import static org.testng.Assert.assertTrue;
+
 public class TestDefaultEntitlementBillingApi {
 	private static final UUID zeroId = new UUID(0L,0L);
 	private static final UUID oneId = new UUID(1L,0L);
@@ -84,7 +87,7 @@ public class TestDefaultEntitlementBillingApi {
         ((DefaultCatalogService)catalogService).loadCatalog();
 	}
 	
-	@BeforeMethod
+	@BeforeMethod(alwaysRun=true)
 	public void setupEveryTime() {
 		bundles = new ArrayList<SubscriptionBundle>();
 		final SubscriptionBundle bundle = new SubscriptionBundleData( zeroId,"TestKey", oneId,  new DateTime().minusDays(4));
@@ -92,13 +95,11 @@ public class TestDefaultEntitlementBillingApi {
 		
 		
 		transitions = new ArrayList<SubscriptionTransition>();
-		
-		
 		subscriptions = new ArrayList<Subscription>();
 		
 		SubscriptionBuilder builder = new SubscriptionBuilder();
 		subscriptionStartDate = new DateTime().minusDays(3);
-		builder.setStartDate(subscriptionStartDate);
+		builder.setStartDate(subscriptionStartDate).setId(oneId);
 		subscription = new SubscriptionData(builder) {
 		    public List<SubscriptionTransition> getAllTransitions() {
 		    	return transitions;
@@ -122,33 +123,42 @@ public class TestDefaultEntitlementBillingApi {
 				return subscription;
 
 			}
-			
-			@Override
+
+            @Override
+            public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
 			public SubscriptionBundle getSubscriptionBundleFromId(UUID bundleId) {
 				return bundle;
 			}
-
-
 		};
 
+        assertTrue(true);
 	}
 	
-	@Test
+    @Test(enabled=true, groups="fast")
 	public void testBillingEventsEmpty() {
 		EntitlementDao dao = new BrainDeadMockEntitlementDao() {
 			public List<SubscriptionBundle> getSubscriptionBundleForAccount(
 					UUID accountId) {
 				return new ArrayList<SubscriptionBundle>();
 			}
-			
-		};
+
+            @Override
+            public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
+                throw new UnsupportedOperationException();
+            }
+
+        };
 		AccountUserApi accountApi = new BrainDeadAccountUserApi() ;
 		DefaultEntitlementBillingApi api = new DefaultEntitlementBillingApi(dao,accountApi,catalogService);
 		SortedSet<BillingEvent> events = api.getBillingEventsForAccount(new UUID(0L,0L));
 		Assert.assertEquals(events.size(), 0);
 	}
 	
-	@Test
+    @Test(enabled=true, groups="fast")
 	public void testBillingEventsNoBillingPeriod() throws CatalogApiException {
 		DateTime now = clock.getUTCNow();
 		DateTime then = now.minusDays(1);
@@ -173,7 +183,7 @@ public class TestDefaultEntitlementBillingApi {
 		checkFirstEvent(events, nextPlan, 32, oneId, now, nextPhase, ApiEventType.CREATE.toString());
 	}
 
-	@Test
+    @Test(enabled=true, groups="fast")
 	public void testBillingEventsAnual() throws CatalogApiException {
 		DateTime now = clock.getUTCNow();
 		DateTime then = now.minusDays(1);
@@ -198,11 +208,11 @@ public class TestDefaultEntitlementBillingApi {
 		checkFirstEvent(events, nextPlan, subscription.getStartDate().getDayOfMonth(), oneId, now, nextPhase, ApiEventType.CREATE.toString());
 	}
 	
-	@Test
+    @Test(enabled=true, groups="fast")
 	public void testBillingEventsMonthly() throws CatalogApiException {
 		DateTime now = clock.getUTCNow();
 		DateTime then = now.minusDays(1);
-		Plan nextPlan = catalogService.getFullCatalog().findPlan("shotgun-annual", now);
+		Plan nextPlan = catalogService.getFullCatalog().findPlan("shotgun-monthly", now);
 		PlanPhase nextPhase = nextPlan.getAllPhases()[1];
 		String nextPriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
 		SubscriptionTransition t = new SubscriptionTransitionData(
@@ -223,11 +233,11 @@ public class TestDefaultEntitlementBillingApi {
 		checkFirstEvent(events, nextPlan, 32, oneId, now, nextPhase, ApiEventType.CREATE.toString());
 	}
 	
-	@Test
+    @Test(enabled=true, groups="fast")
 	public void testBillingEventsAddOn() throws CatalogApiException {
 		DateTime now = clock.getUTCNow();
 		DateTime then = now.minusDays(1);
-		Plan nextPlan = catalogService.getFullCatalog().findPlan("shotgun-annual", now);
+		Plan nextPlan = catalogService.getFullCatalog().findPlan("laser-scope-monthly", now);
 		PlanPhase nextPhase = nextPlan.getAllPhases()[0];
 		String nextPriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
 		SubscriptionTransition t = new SubscriptionTransitionData(
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java
index b0483bb..912f186 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java
@@ -67,8 +67,8 @@ import com.ning.billing.entitlement.events.user.ApiEventType;
 import com.ning.billing.lifecycle.KillbillService.ServiceException;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.ClockMock;
-import com.ning.billing.util.eventbus.DefaultEventBusService;
-import com.ning.billing.util.eventbus.EventBusService;
+import com.ning.billing.util.bus.DefaultBusService;
+import com.ning.billing.util.bus.BusService;
 
 
 public abstract class TestApiBase {
@@ -87,7 +87,7 @@ public abstract class TestApiBase {
     protected EntitlementConfig config;
     protected EntitlementDao dao;
     protected ClockMock clock;
-    protected EventBusService busService;
+    protected BusService busService;
 
     protected AccountData accountData;
     protected Catalog catalog;
@@ -109,8 +109,8 @@ public abstract class TestApiBase {
     @AfterClass(groups={"setup"})
     public void tearDown() {
         try {
-            busService.getEventBus().register(testListener);
-            ((DefaultEventBusService) busService).stopBus();
+            busService.getBus().register(testListener);
+            ((DefaultBusService) busService).stopBus();
         } catch (Exception e) {
             log.warn("Failed to tearDown test properly ", e);
         }
@@ -125,14 +125,13 @@ public abstract class TestApiBase {
 
         entitlementService = g.getInstance(EntitlementService.class);
         catalogService = g.getInstance(CatalogService.class);
-        busService = g.getInstance(EventBusService.class);
+        busService = g.getInstance(BusService.class);
         config = g.getInstance(EntitlementConfig.class);
         dao = g.getInstance(EntitlementDao.class);
         clock = (ClockMock) g.getInstance(Clock.class);
         try {
-
             ((DefaultCatalogService) catalogService).loadCatalog();
-            ((DefaultEventBusService) busService).startBus();
+            ((DefaultBusService) busService).startBus();
             ((Engine) entitlementService).initialize();
             init();
         } catch (EntitlementUserApiException e) {
@@ -152,7 +151,7 @@ public abstract class TestApiBase {
         assertNotNull(catalog);
 
 
-        testListener = new ApiTestListener(busService.getEventBus());
+        testListener = new ApiTestListener(busService.getBus());
         entitlementApi = entitlementService.getUserApi();
         billingApi = entitlementService.getBillingApi();
         migrationApi = entitlementService.getMigrationApi();
@@ -170,7 +169,7 @@ public abstract class TestApiBase {
         clock.resetDeltaFromReality();
         ((MockEntitlementDao) dao).reset();
         try {
-            busService.getEventBus().register(testListener);
+            busService.getBus().register(testListener);
             UUID accountId = UUID.randomUUID();
             bundle = entitlementApi.createBundleForAccount(accountId, "myDefaultBundle");
         } catch (Exception e) {
@@ -242,6 +241,11 @@ public abstract class TestApiBase {
             public int getNumber() {
                 return days;
             }
+
+            @Override
+            public DateTime addToDateTime(DateTime dateTime) {
+                return null;
+            }
         };
         return result;
     }
@@ -256,6 +260,11 @@ public abstract class TestApiBase {
             public int getNumber() {
                 return months;
             }
+
+            @Override
+            public DateTime addToDateTime(DateTime dateTime) {
+                return null;  //To change body of implemented methods use File | Settings | File Templates.
+            }
         };
         return result;
     }
@@ -271,6 +280,11 @@ public abstract class TestApiBase {
             public int getNumber() {
                 return years;
             }
+
+            @Override
+            public DateTime addToDateTime(DateTime dateTime) {
+                return null;  //To change body of implemented methods use File | Settings | File Templates.
+            }
         };
         return result;
     }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java
index 3326bc8..1e725bf 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java
@@ -22,6 +22,7 @@ import static org.testng.Assert.assertTrue;
 import static org.testng.Assert.assertFalse;
 
 import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
 import com.ning.billing.util.clock.DefaultClock;
 import org.joda.time.DateTime;
 import org.testng.Assert;
@@ -80,7 +81,7 @@ public abstract class TestUserApiCancel extends TestApiBase {
     }
 
 
-    protected void testCancelSubscriptionEOTWithChargeThroughDate() {
+    protected void testCancelSubscriptionEOTWithChargeThroughDate() throws EntitlementBillingApiException {
         log.info("Starting testCancelSubscriptionEOTWithChargeThroughDate");
 
         try {
@@ -176,7 +177,7 @@ public abstract class TestUserApiCancel extends TestApiBase {
     // Similar test to testCancelSubscriptionEOTWithChargeThroughDate except we uncancel and check things
     // are as they used to be and we can move forward without hitting cancellation
     //
-    protected void testUncancel() {
+    protected void testUncancel() throws EntitlementBillingApiException {
 
         log.info("Starting testUncancel");
 
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelMemory.java
index 630d925..dbcc680 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelMemory.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelMemory.java
@@ -19,6 +19,7 @@ package com.ning.billing.entitlement.api.user;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
 import com.ning.billing.entitlement.glue.MockEngineModuleMemory;
 import org.testng.annotations.Test;
 
@@ -38,7 +39,7 @@ public class TestUserApiCancelMemory extends TestUserApiCancel {
 
     @Override
     @Test(enabled=true, groups={"fast"})
-    public void testCancelSubscriptionEOTWithChargeThroughDate() {
+    public void testCancelSubscriptionEOTWithChargeThroughDate() throws EntitlementBillingApiException {
         super.testCancelSubscriptionEOTWithChargeThroughDate();
     }
 
@@ -50,7 +51,7 @@ public class TestUserApiCancelMemory extends TestUserApiCancel {
 
     @Override
     @Test(enabled=true, groups={"fast"})
-    public void testUncancel() {
+    public void testUncancel() throws EntitlementBillingApiException {
         super.testUncancel();
     }
 }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelSql.java
index 87491c7..840f357 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelSql.java
@@ -19,6 +19,7 @@ package com.ning.billing.entitlement.api.user;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
 import com.ning.billing.entitlement.glue.MockEngineModuleSql;
 import org.testng.annotations.Test;
 
@@ -33,7 +34,7 @@ public class TestUserApiCancelSql extends TestUserApiCancel {
     }
 
     @Test(enabled= false, groups={"stress"})
-    public void stressTest() {
+    public void stressTest() throws EntitlementBillingApiException {
         for (int i = 0; i < MAX_STRESS_ITERATIONS; i++) {
             cleanupTest();
             setupTest();
@@ -55,7 +56,7 @@ public class TestUserApiCancelSql extends TestUserApiCancel {
 
     @Override
     @Test(enabled=true, groups={"sql"})
-    public void testCancelSubscriptionEOTWithChargeThroughDate() {
+    public void testCancelSubscriptionEOTWithChargeThroughDate() throws EntitlementBillingApiException {
         super.testCancelSubscriptionEOTWithChargeThroughDate();
     }
 
@@ -67,7 +68,7 @@ public class TestUserApiCancelSql extends TestUserApiCancel {
 
     @Override
     @Test(enabled=true, groups={"sql"})
-    public void testUncancel() {
+    public void testUncancel() throws EntitlementBillingApiException {
         super.testUncancel();
     }
 
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java
index 51c3d8b..78616be 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java
@@ -37,6 +37,7 @@ import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.TestApiBase;
 
 import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
 import com.ning.billing.entitlement.events.EntitlementEvent;
 import com.ning.billing.entitlement.events.user.ApiEvent;
 import com.ning.billing.util.clock.DefaultClock;
@@ -100,12 +101,12 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
     }
 
 
-    protected void testChangePlanBundleAlignEOTWithChargeThroughDate() {
+    protected void testChangePlanBundleAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
         testChangePlanBundleAlignEOTWithChargeThroughDate("Shotgun", BillingPeriod.ANNUAL, "gunclubDiscount", "Pistol", BillingPeriod.ANNUAL, "gunclubDiscount");
     }
 
     private void testChangePlanBundleAlignEOTWithChargeThroughDate(String fromProd, BillingPeriod fromTerm, String fromPlanSet,
-            String toProd, BillingPeriod toTerm, String toPlanSet) {
+            String toProd, BillingPeriod toTerm, String toPlanSet) throws EntitlementBillingApiException {
 
         log.info("Starting testChangeSubscriptionEOTWithChargeThroughDate");
         try {
@@ -216,12 +217,12 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
     }
 
 
-    protected void testChangePlanChangePlanAlignEOTWithChargeThroughDate() {
+    protected void testChangePlanChangePlanAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
         tChangePlanChangePlanAlignEOTWithChargeThroughDate("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, "Assault-Rifle", BillingPeriod.ANNUAL, "rescue");
     }
 
     private void tChangePlanChangePlanAlignEOTWithChargeThroughDate(String fromProd, BillingPeriod fromTerm, String fromPlanSet,
-            String toProd, BillingPeriod toTerm, String toPlanSet) {
+            String toProd, BillingPeriod toTerm, String toPlanSet) throws EntitlementBillingApiException {
 
         log.info("Starting testChangePlanBundleAlignEOTWithChargeThroughDate");
 
@@ -297,7 +298,7 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
         }
     }
 
-    protected void testMultipleChangeLastIMM() {
+    protected void testMultipleChangeLastIMM() throws EntitlementBillingApiException {
 
         try {
             SubscriptionData subscription = createSubscription("Assault-Rifle", BillingPeriod.MONTHLY, "gunclubDiscount");
@@ -344,7 +345,7 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
         }
     }
 
-    protected void testMultipleChangeLastEOT() {
+    protected void testMultipleChangeLastEOT() throws EntitlementBillingApiException {
 
         try {
 
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanMemory.java
index aecaaac..253da07 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanMemory.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanMemory.java
@@ -19,6 +19,7 @@ package com.ning.billing.entitlement.api.user;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
 import com.ning.billing.entitlement.glue.MockEngineModuleMemory;
 import org.testng.annotations.Test;
 
@@ -38,7 +39,7 @@ public class TestUserApiChangePlanMemory extends TestUserApiChangePlan {
 
     @Override
     @Test(enabled=true, groups={"fast"})
-    public void testChangePlanBundleAlignEOTWithChargeThroughDate() {
+    public void testChangePlanBundleAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
         super.testChangePlanBundleAlignEOTWithChargeThroughDate();
     }
 
@@ -50,20 +51,20 @@ public class TestUserApiChangePlanMemory extends TestUserApiChangePlan {
 
     @Override
     @Test(enabled=true, groups={"fast"})
-    public void testMultipleChangeLastIMM() {
+    public void testMultipleChangeLastIMM() throws EntitlementBillingApiException {
         super.testMultipleChangeLastIMM();
     }
 
     @Override
     @Test(enabled=true, groups={"fast"})
-    public void testMultipleChangeLastEOT() {
+    public void testMultipleChangeLastEOT() throws EntitlementBillingApiException {
         super.testMultipleChangeLastEOT();
     }
 
     // Set to false until we implement rescue example.
     @Override
     @Test(enabled=false, groups={"fast"})
-    public void testChangePlanChangePlanAlignEOTWithChargeThroughDate() {
+    public void testChangePlanChangePlanAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
         super.testChangePlanChangePlanAlignEOTWithChargeThroughDate();
     }
 }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanSql.java
index f5ad803..92aa652 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanSql.java
@@ -19,6 +19,7 @@ package com.ning.billing.entitlement.api.user;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
 import com.ning.billing.entitlement.glue.MockEngineModuleSql;
 import org.testng.annotations.Test;
 
@@ -32,7 +33,7 @@ public class TestUserApiChangePlanSql extends TestUserApiChangePlan {
     }
 
     @Test(enabled= true, groups={"stress"})
-    public void stressTest() {
+    public void stressTest() throws EntitlementBillingApiException {
         for (int i = 0; i < MAX_STRESS_ITERATIONS; i++) {
             cleanupTest();
             setupTest();
@@ -60,7 +61,7 @@ public class TestUserApiChangePlanSql extends TestUserApiChangePlan {
 
     @Override
     @Test(enabled=true, groups={"sql"})
-    public void testChangePlanBundleAlignEOTWithChargeThroughDate() {
+    public void testChangePlanBundleAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
         super.testChangePlanBundleAlignEOTWithChargeThroughDate();
     }
 
@@ -72,20 +73,20 @@ public class TestUserApiChangePlanSql extends TestUserApiChangePlan {
 
     @Override
     @Test(enabled=true, groups={"sql"})
-    public void testMultipleChangeLastIMM() {
+    public void testMultipleChangeLastIMM() throws EntitlementBillingApiException {
         super.testMultipleChangeLastIMM();
     }
 
     @Override
     @Test(enabled=true, groups={"sql"})
-    public void testMultipleChangeLastEOT() {
+    public void testMultipleChangeLastEOT() throws EntitlementBillingApiException {
         super.testMultipleChangeLastEOT();
     }
 
     // rescue not implemented yet
     @Override
     @Test(enabled=false, groups={"sql"})
-    public void testChangePlanChangePlanAlignEOTWithChargeThroughDate() {
+    public void testChangePlanChangePlanAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
         super.testChangePlanChangePlanAlignEOTWithChargeThroughDate();
     }
 
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java
index 4a8b4b1..7fceab9 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java
@@ -29,6 +29,7 @@ import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.TestApiBase;
 
 import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
 import com.ning.billing.entitlement.glue.MockEngineModuleSql;
 import com.ning.billing.util.clock.DefaultClock;
 import org.joda.time.DateTime;
@@ -64,7 +65,7 @@ public class TestUserApiDemos extends TestApiBase {
      *  8. Cancel EOT
      */
     @Test(enabled=true, groups="demos")
-    public void testDemo1() {
+    public void testDemo1() throws EntitlementBillingApiException {
 
         try {
             System.out.println("DEMO 1 START");
@@ -189,7 +190,7 @@ public class TestUserApiDemos extends TestApiBase {
     }
 
     @Test(enabled= true, groups={"stress"})
-    public void stressTest() {
+    public void stressTest() throws EntitlementBillingApiException {
         for (int i = 0; i < 100; i++) {
             cleanupTest();
             setupTest();
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java
index 84d1031..f69b36a 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java
@@ -26,6 +26,7 @@ import com.ning.billing.catalog.api.PlanPhase;
 
 import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.api.TestApiBase;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
 import com.ning.billing.entitlement.glue.MockEngineModuleSql;
 import com.ning.billing.util.clock.DefaultClock;
 import org.joda.time.DateTime;
@@ -42,7 +43,7 @@ public class TestUserApiScenarios extends TestApiBase {
     }
 
     @Test(enabled=true)
-    public void testChangeIMMCancelUncancelChangeEOT() {
+    public void testChangeIMMCancelUncancelChangeEOT() throws EntitlementBillingApiException {
 
         log.info("Starting testChangeIMMCancelUncancelChangeEOT");
 
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java
index 86e458f..6395470 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java
@@ -16,20 +16,30 @@
 
 package com.ning.billing.entitlement.engine.dao;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.TreeSet;
+import java.util.UUID;
+import org.joda.time.DateTime;
+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.catalog.api.ProductCategory;
 import com.ning.billing.catalog.api.TimeUnit;
 import com.ning.billing.config.EntitlementConfig;
-
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
 import com.ning.billing.entitlement.api.migration.AccountMigrationData;
 import com.ning.billing.entitlement.api.migration.AccountMigrationData.BundleMigrationData;
 import com.ning.billing.entitlement.api.migration.AccountMigrationData.SubscriptionMigrationData;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
-import com.ning.billing.entitlement.api.user.SubscriptionData;
 import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
 import com.ning.billing.entitlement.api.user.SubscriptionFactory;
-
 import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
 import com.ning.billing.entitlement.engine.core.Engine;
 import com.ning.billing.entitlement.events.EntitlementEvent;
@@ -38,20 +48,10 @@ import com.ning.billing.entitlement.events.user.ApiEvent;
 import com.ning.billing.entitlement.events.user.ApiEventType;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.notificationq.NotificationKey;
-import com.ning.billing.util.notificationq.NotificationLifecycle;
 import com.ning.billing.util.notificationq.NotificationQueue;
 import com.ning.billing.util.notificationq.NotificationQueueService;
 import com.ning.billing.util.notificationq.NotificationQueueService.NoSuchNotificationQueue;
 
-import org.joda.time.DateTime;
-import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.*;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
 public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlementDao {
 
     protected final static Logger log = LoggerFactory.getLogger(EntitlementDao.class);
@@ -65,7 +65,9 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     private final NotificationQueueService notificationQueueService;
 
     @Inject
-    public MockEntitlementDaoMemory(Clock clock, EntitlementConfig config, SubscriptionFactory factory, NotificationQueueService notificationQueueService) {
+    public MockEntitlementDaoMemory(final Clock clock, final EntitlementConfig config,
+                                    final SubscriptionFactory factory,
+                                    final NotificationQueueService notificationQueueService) {
         super();
         this.clock = clock;
         this.config = config;
@@ -84,9 +86,9 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public List<SubscriptionBundle> getSubscriptionBundleForAccount(UUID accountId) {
+    public List<SubscriptionBundle> getSubscriptionBundleForAccount(final UUID accountId) {
         List<SubscriptionBundle> results = new ArrayList<SubscriptionBundle>();
-        for (SubscriptionBundle cur : bundles) {
+        for (final SubscriptionBundle cur : bundles) {
             if (cur.getAccountId().equals(accountId)) {
                 results.add(cur);
             }
@@ -95,8 +97,8 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public SubscriptionBundle getSubscriptionBundleFromId(UUID bundleId) {
-        for (SubscriptionBundle cur : bundles) {
+    public SubscriptionBundle getSubscriptionBundleFromId(final UUID bundleId) {
+        for (final SubscriptionBundle cur : bundles) {
             if (cur.getId().equals(bundleId)) {
                 return cur;
             }
@@ -105,8 +107,8 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public SubscriptionBundle getSubscriptionBundleFromKey(String bundleKey) {
-        for (SubscriptionBundle cur : bundles) {
+    public SubscriptionBundle getSubscriptionBundleFromKey(final String bundleKey) {
+        for (final SubscriptionBundle cur : bundles) {
             if (cur.getKey().equals(bundleKey)) {
                 return cur;
             }
@@ -116,14 +118,14 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
 
 
     @Override
-    public SubscriptionBundle createSubscriptionBundle(SubscriptionBundleData bundle) {
+    public SubscriptionBundle createSubscriptionBundle(final SubscriptionBundleData bundle) {
         bundles.add(bundle);
         return getSubscriptionBundleFromId(bundle.getId());
     }
 
     @Override
-    public Subscription getSubscriptionFromId(UUID subscriptionId) {
-        for (Subscription cur : subscriptions) {
+    public Subscription getSubscriptionFromId(final UUID subscriptionId) {
+        for (final Subscription cur : subscriptions) {
             if (cur.getId().equals(subscriptionId)) {
                 return buildSubscription((SubscriptionData) cur);
             }
@@ -132,9 +134,14 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public List<Subscription> getSubscriptionsForKey(String bundleKey) {
+    public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public List<Subscription> getSubscriptionsForKey(final String bundleKey) {
 
-        for (SubscriptionBundle cur : bundles) {
+        for (final SubscriptionBundle cur : bundles) {
             if (cur.getKey().equals(bundleKey)) {
                 return getSubscriptions(cur.getId());
             }
@@ -144,11 +151,11 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
 
 
     @Override
-    public void createSubscription(SubscriptionData subscription, List<EntitlementEvent> initalEvents) {
+    public void createSubscription(final SubscriptionData subscription, final List<EntitlementEvent> initialEvents) {
 
         synchronized(events) {
-            events.addAll(initalEvents);
-            for (final EntitlementEvent cur : initalEvents) {
+            events.addAll(initialEvents);
+            for (final EntitlementEvent cur : initialEvents) {
                 recordFutureNotificationFromTransaction(null, cur.getEffectiveDate(), new NotificationKey() {
                     @Override
                     public String toString() {
@@ -162,10 +169,10 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public List<Subscription> getSubscriptions(UUID bundleId) {
+    public List<Subscription> getSubscriptions(final UUID bundleId) {
 
         List<Subscription> results = new ArrayList<Subscription>();
-        for (Subscription cur : subscriptions) {
+        for (final Subscription cur : subscriptions) {
             if (cur.getBundleId().equals(bundleId)) {
                 results.add(buildSubscription((SubscriptionData) cur));
             }
@@ -174,10 +181,10 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public List<EntitlementEvent> getEventsForSubscription(UUID subscriptionId) {
+    public List<EntitlementEvent> getEventsForSubscription(final UUID subscriptionId) {
         synchronized(events) {
             List<EntitlementEvent> results = new LinkedList<EntitlementEvent>();
-            for (EntitlementEvent cur : events) {
+            for (final EntitlementEvent cur : events) {
                 if (cur.getSubscriptionId().equals(subscriptionId)) {
                     results.add(cur);
                 }
@@ -187,10 +194,10 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public List<EntitlementEvent> getPendingEventsForSubscription(UUID subscriptionId) {
+    public List<EntitlementEvent> getPendingEventsForSubscription(final UUID subscriptionId) {
         synchronized(events) {
             List<EntitlementEvent> results = new LinkedList<EntitlementEvent>();
-            for (EntitlementEvent cur : events) {
+            for (final EntitlementEvent cur : events) {
                 if (cur.isActive() &&
                         cur.getEffectiveDate().isAfter(clock.getUTCNow()) &&
                             cur.getSubscriptionId().equals(subscriptionId)) {
@@ -203,8 +210,8 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
 
 
     @Override
-    public Subscription getBaseSubscription(UUID bundleId) {
-        for (Subscription cur : subscriptions) {
+    public Subscription getBaseSubscription(final UUID bundleId) {
+        for (final Subscription cur : subscriptions) {
             if (cur.getBundleId().equals(bundleId) &&
                     cur.getCurrentPlan().getProduct().getCategory() == ProductCategory.BASE) {
                 return buildSubscription((SubscriptionData) cur);
@@ -214,19 +221,19 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public void createNextPhaseEvent(UUID subscriptionId, EntitlementEvent nextPhase) {
+    public void createNextPhaseEvent(final UUID subscriptionId, final EntitlementEvent nextPhase) {
         cancelNextPhaseEvent(subscriptionId);
         insertEvent(nextPhase);
     }
 
 
 
-    private Subscription buildSubscription(SubscriptionData in) {
+    private Subscription buildSubscription(final SubscriptionData in) {
         return factory.createSubscription(new SubscriptionBuilder(in), getEventsForSubscription(in.getId()));
     }
 
     @Override
-    public void updateSubscription(SubscriptionData subscription) {
+    public void updateSubscription(final SubscriptionData subscription) {
 
         boolean found = false;
         Iterator<Subscription> it = subscriptions.iterator();
@@ -244,7 +251,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public void cancelSubscription(UUID subscriptionId, EntitlementEvent cancelEvent) {
+    public void cancelSubscription(final UUID subscriptionId, final EntitlementEvent cancelEvent) {
         synchronized (cancelEvent) {
             cancelNextPhaseEvent(subscriptionId);
             insertEvent(cancelEvent);
@@ -252,7 +259,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public void changePlan(UUID subscriptionId, List<EntitlementEvent> changeEvents) {
+    public void changePlan(final UUID subscriptionId, final List<EntitlementEvent> changeEvents) {
         synchronized(events) {
             cancelNextChangeEvent(subscriptionId);
             cancelNextPhaseEvent(subscriptionId);
@@ -280,7 +287,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
         }
     }
 
-    private void cancelNextPhaseEvent(UUID subscriptionId) {
+    private void cancelNextPhaseEvent(final UUID subscriptionId) {
 
         Subscription curSubscription = getSubscriptionFromId(subscriptionId);
         if (curSubscription.getCurrentPhase() == null ||
@@ -307,7 +314,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
 
-    private void cancelNextChangeEvent(UUID subscriptionId) {
+    private void cancelNextChangeEvent(final UUID subscriptionId) {
 
         synchronized(events) {
 
@@ -328,7 +335,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public void uncancelSubscription(UUID subscriptionId, List<EntitlementEvent> uncancelEvents) {
+    public void uncancelSubscription(final UUID subscriptionId, final List<EntitlementEvent> uncancelEvents) {
 
         synchronized (events) {
             boolean foundCancel = false;
@@ -346,7 +353,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
                 }
             }
             if (foundCancel) {
-                for (EntitlementEvent cur : uncancelEvents) {
+                for (final EntitlementEvent cur : uncancelEvents) {
                     insertEvent(cur);
                 }
             }
@@ -360,9 +367,9 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
 
             undoMigration(accountId);
 
-            for (BundleMigrationData curBundle : accountData.getData()) {
+            for (final BundleMigrationData curBundle : accountData.getData()) {
                 SubscriptionBundleData bundleData = curBundle.getData();
-                for (SubscriptionMigrationData curSubscription : curBundle.getSubscriptions()) {
+                for (final SubscriptionMigrationData curSubscription : curBundle.getSubscriptions()) {
                     SubscriptionData subData = curSubscription.getData();
                     for (final EntitlementEvent curEvent : curSubscription.getInitialEvents()) {
                         events.add(curEvent);
@@ -382,15 +389,15 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public void undoMigration(UUID accountId) {
+    public void undoMigration(final UUID accountId) {
         synchronized(events) {
 
             List<SubscriptionBundle> allBundles = getSubscriptionBundleForAccount(accountId);
-            for (SubscriptionBundle bundle : allBundles) {
+            for (final SubscriptionBundle bundle : allBundles) {
                 List<Subscription> allSubscriptions = getSubscriptions(bundle.getId());
-                for (Subscription subscription : allSubscriptions) {
+                for (final Subscription subscription : allSubscriptions) {
                     List<EntitlementEvent> allEvents = getEventsForSubscription(subscription.getId());
-                    for (EntitlementEvent event : allEvents) {
+                    for (final EntitlementEvent event : allEvents) {
                         events.remove(event);
                     }
                     subscriptions.remove(subscription);
@@ -402,9 +409,9 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public EntitlementEvent getEventById(UUID eventId) {
+    public EntitlementEvent getEventById(final UUID eventId) {
         synchronized(events) {
-            for (EntitlementEvent cur : events) {
+            for (final EntitlementEvent cur : events) {
                 if (cur.getId().equals(eventId)) {
                     return cur;
                 }
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 503fd1a..2204274 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
@@ -16,25 +16,24 @@
 
 package com.ning.billing.entitlement.engine.dao;
 
-import com.google.inject.Inject;
-import com.ning.billing.config.EntitlementConfig;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory;
-import com.ning.billing.util.clock.Clock;
-import com.ning.billing.util.notificationq.NotificationQueueService;
-
-import org.skife.jdbi.v2.DBI;
+import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.TransactionStatus;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
 import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
 import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
 
+import com.google.inject.Inject;
+import com.ning.billing.entitlement.api.user.SubscriptionFactory;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.notificationq.NotificationQueueService;
+
 public class MockEntitlementDaoSql extends EntitlementSqlDao implements MockEntitlementDao {
 
     private final ResetSqlDao resetDao;
 
     @Inject
-    public MockEntitlementDaoSql(DBI dbi, Clock clock, SubscriptionFactory factory, NotificationQueueService notificationQueueService) {
+    public MockEntitlementDaoSql(IDBI dbi, Clock clock, SubscriptionFactory factory, NotificationQueueService notificationQueueService) {
         super(dbi, clock, factory, notificationQueueService);
         this.resetDao = dbi.onDemand(ResetSqlDao.class);
     }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModule.java b/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModule.java
index a00183b..d46fe83 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModule.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModule.java
@@ -19,21 +19,16 @@ package com.ning.billing.entitlement.glue;
 import com.ning.billing.account.glue.AccountModuleWithMocks;
 import com.ning.billing.catalog.glue.CatalogModule;
 import com.ning.billing.util.clock.MockClockModule;
-import com.ning.billing.util.glue.EventBusModule;
+import com.ning.billing.util.glue.BusModule;
 
 public class MockEngineModule extends EntitlementModule {
 
-    protected void installModulesForTests() {
-        install(new EventBusModule());
-        install(new CatalogModule());
-        install(new AccountModuleWithMocks());
-        install(new MockClockModule());
-    }
-
     @Override
     protected void configure() {
         super.configure();
-        installModulesForTests();
+        install(new BusModule());
+        install(new CatalogModule());
+        install(new AccountModuleWithMocks());
+        install(new MockClockModule());
     }
-
 }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleSql.java
index 2532c6f..e9e6134 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleSql.java
@@ -25,7 +25,7 @@ import com.ning.billing.util.clock.ClockMock;
 import com.ning.billing.util.glue.NotificationQueueModule;
 
 import org.skife.config.ConfigurationObjectFactory;
-import org.skife.jdbi.v2.DBI;
+import org.skife.jdbi.v2.IDBI;
 
 public class MockEngineModuleSql extends MockEngineModule {
 
@@ -36,7 +36,7 @@ public class MockEngineModuleSql extends MockEngineModule {
     }
 
     protected void installDBI() {
-        bind(DBI.class).toProvider(DBIProvider.class).asEagerSingleton();
+        bind(IDBI.class).toProvider(DBIProvider.class).asEagerSingleton();
         final DbiConfig config = new ConfigurationObjectFactory(System.getProperties()).build(DbiConfig.class);
         bind(DbiConfig.class).toInstance(config);
     }

invoice/pom.xml 17(+15 -2)

diff --git a/invoice/pom.xml b/invoice/pom.xml
index cdc1725..e685de0 100644
--- a/invoice/pom.xml
+++ b/invoice/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.3-SNAPSHOT</version>
+        <version>0.1.5-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-invoice</artifactId>
@@ -21,6 +21,11 @@
     <packaging>jar</packaging>
     <dependencies>
         <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>com.ning.billing</groupId>
             <artifactId>killbill-api</artifactId>
         </dependency>
@@ -48,7 +53,6 @@
             <type>test-jar</type>
             <scope>test</scope>
         </dependency>
-        
         <dependency>
             <groupId>org.testng</groupId>
             <artifactId>testng</artifactId>
@@ -94,6 +98,15 @@
             <artifactId>guice</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.jayway.awaitility</groupId>
+            <artifactId>awaitility</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
     <build>
     </build>
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/DefaultInvoiceService.java b/invoice/src/main/java/com/ning/billing/invoice/api/DefaultInvoiceService.java
index b7bc657..6f033db 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/DefaultInvoiceService.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/DefaultInvoiceService.java
@@ -17,19 +17,31 @@
 package com.ning.billing.invoice.api;
 
 import com.google.inject.Inject;
+import com.ning.billing.invoice.InvoiceListener;
+import com.ning.billing.invoice.notification.NextBillingDateNotifier;
 import com.ning.billing.lifecycle.LifecycleHandlerType;
+import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
+import com.ning.billing.util.bus.Bus;
 
 public class DefaultInvoiceService implements InvoiceService {
-    private static final String INVOICE_SERVICE_NAME = "invoice-service";
+
+    public static final String INVOICE_SERVICE_NAME = "invoice-service";
     private final InvoiceUserApi userApi;
     private final InvoicePaymentApi paymentApi;
+    private final NextBillingDateNotifier dateNotifier;
+    private final InvoiceListener invoiceListener;
+    private final Bus eventBus;
 
     @Inject
-    public DefaultInvoiceService(InvoiceUserApi userApi, InvoicePaymentApi paymentApi) {
+    public DefaultInvoiceService(InvoiceListener invoiceListener, Bus eventBus, InvoiceUserApi userApi, InvoicePaymentApi paymentApi, NextBillingDateNotifier dateNotifier) {
+        this.invoiceListener = invoiceListener;
+        this.eventBus = eventBus;
         this.userApi = userApi;
         this.paymentApi = paymentApi;
+        this.dateNotifier = dateNotifier;
     }
 
+
     @Override
     public String getName() {
         return INVOICE_SERVICE_NAME;
@@ -47,5 +59,34 @@ public class DefaultInvoiceService implements InvoiceService {
 
     @LifecycleHandlerType(LifecycleHandlerType.LifecycleLevel.INIT_SERVICE)
     public void initialize() {
+        dateNotifier.initialize();
+    }
+
+    @LifecycleHandlerType(LifecycleLevel.START_SERVICE)
+    public void start() {
+        dateNotifier.start();
+    }
+
+    @LifecycleHandlerType(LifecycleHandlerType.LifecycleLevel.REGISTER_EVENTS)
+    public void registerForNotifications() {
+        try {
+            eventBus.register(invoiceListener);
+        } catch (Bus.EventBusException e) {
+            throw new RuntimeException("Unable to register to the EventBus!", e);
+        }
+    }
+
+    @LifecycleHandlerType(LifecycleHandlerType.LifecycleLevel.UNREGISTER_EVENTS)
+    public void unregisterForNotifications() {
+        try {
+            eventBus.unregister(invoiceListener);
+        } catch (Bus.EventBusException e) {
+            throw new RuntimeException("Unable to unregister to the EventBus!", e);
+        }
+    }
+
+    @LifecycleHandlerType(LifecycleLevel.STOP_SERVICE)
+    public void stop() {
+        dateNotifier.stop();
     }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java b/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
index 31006ea..340e682 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
@@ -17,24 +17,24 @@
 
 package com.ning.billing.invoice.api.invoice;
 
-import java.math.BigDecimal;
-import java.util.List;
-import java.util.UUID;
-
-import org.joda.time.DateTime;
-
 import com.google.inject.Inject;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoicePayment;
 import com.ning.billing.invoice.api.InvoicePaymentApi;
 import com.ning.billing.invoice.dao.InvoiceDao;
-import com.ning.billing.payment.api.InvoicePayment;
+import com.ning.billing.invoice.model.DefaultInvoicePayment;
+import org.joda.time.DateTime;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.UUID;
 
 public class DefaultInvoicePaymentApi implements InvoicePaymentApi {
     private final InvoiceDao dao;
 
     @Inject
-    public DefaultInvoicePaymentApi(InvoiceDao dao) {
+    public DefaultInvoicePaymentApi(final InvoiceDao dao) {
         this.dao = dao;
     }
 
@@ -49,18 +49,18 @@ public class DefaultInvoicePaymentApi implements InvoicePaymentApi {
 //    }
 
     @Override
-    public List<Invoice> getInvoicesByAccount(UUID accountId) {
-        return dao.getInvoicesByAccount(accountId.toString());
+    public List<Invoice> getInvoicesByAccount(final UUID accountId) {
+        return dao.getInvoicesByAccount(accountId);
     }
 
     @Override
-    public Invoice getInvoice(UUID invoiceId) {
-        return dao.getById(invoiceId.toString());
+    public Invoice getInvoice(final UUID invoiceId) {
+        return dao.getById(invoiceId);
     }
 
     @Override
     public Invoice getInvoiceForPaymentAttemptId(UUID paymentAttemptId) {
-        String invoiceIdStr = dao.getInvoiceIdByPaymentAttemptId(paymentAttemptId);
+        UUID invoiceIdStr = dao.getInvoiceIdByPaymentAttemptId(paymentAttemptId);
         return invoiceIdStr == null ? null : dao.getById(invoiceIdStr);
     }
 
@@ -70,15 +70,14 @@ public class DefaultInvoicePaymentApi implements InvoicePaymentApi {
     }
 
     @Override
-    public void notifyOfPaymentAttempt(UUID invoiceId, BigDecimal amountOutstanding, Currency currency, UUID paymentAttemptId, DateTime paymentAttemptDate) {
-        InvoicePayment invoicePayment = new InvoicePayment(invoiceId, amountOutstanding, currency, paymentAttemptId, paymentAttemptDate);
+    public void notifyOfPaymentAttempt(UUID invoiceId, BigDecimal amount, Currency currency, UUID paymentAttemptId, DateTime paymentAttemptDate) {
+        InvoicePayment invoicePayment = new DefaultInvoicePayment(paymentAttemptId, invoiceId, paymentAttemptDate, amount, currency, null, null);
         dao.notifyOfPaymentAttempt(invoicePayment);
     }
 
     @Override
     public void notifyOfPaymentAttempt(UUID invoiceId, UUID paymentAttemptId, DateTime paymentAttemptDate) {
-        InvoicePayment invoicePayment = new InvoicePayment(invoiceId, null, null, paymentAttemptId, paymentAttemptDate);
+        InvoicePayment invoicePayment = new DefaultInvoicePayment(paymentAttemptId, invoiceId, paymentAttemptDate);
         dao.notifyOfPaymentAttempt(invoicePayment);
     }
-
-}
+}
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/test/DefaultInvoiceTestApi.java b/invoice/src/main/java/com/ning/billing/invoice/api/test/DefaultInvoiceTestApi.java
new file mode 100644
index 0000000..bbec57f
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/test/DefaultInvoiceTestApi.java
@@ -0,0 +1,35 @@
+/*
+ * 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.api.test;
+
+import com.google.inject.Inject;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.dao.InvoiceDao;
+
+public class DefaultInvoiceTestApi implements InvoiceTestApi {
+    private final InvoiceDao invoiceDao;
+
+    @Inject
+    public DefaultInvoiceTestApi(InvoiceDao invoiceDao) {
+        this.invoiceDao = invoiceDao;
+    }
+
+    @Override
+    public void create(Invoice invoice) {
+        invoiceDao.create(invoice);
+    }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
index 3bf9846..629f939 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
@@ -20,46 +20,60 @@ import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
 
+import com.ning.billing.invoice.api.InvoicePayment;
 import org.joda.time.DateTime;
-
 import com.google.inject.Inject;
 import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.invoice.api.InvoiceUserApi;
 import com.ning.billing.invoice.dao.InvoiceDao;
-import com.ning.billing.payment.api.InvoicePayment;
 
 public class DefaultInvoiceUserApi implements InvoiceUserApi {
     private final InvoiceDao dao;
 
     @Inject
-    public DefaultInvoiceUserApi(InvoiceDao dao) {
+    public DefaultInvoiceUserApi(final InvoiceDao dao) {
         this.dao = dao;
     }
 
     @Override
-    public List<UUID> getInvoicesForPayment(DateTime targetDate, int numberOfDays) {
-        return dao.getInvoicesForPayment(targetDate.toDate(), numberOfDays);
+    public List<UUID> getInvoicesForPayment(final DateTime targetDate, final int numberOfDays) {
+        return dao.getInvoicesForPayment(targetDate, numberOfDays);
     }
 
     @Override
-    public List<Invoice> getInvoicesByAccount(UUID accountId) {
-        return dao.getInvoicesByAccount(accountId.toString());
+    public List<Invoice> getInvoicesByAccount(final UUID accountId) {
+        return dao.getInvoicesByAccount(accountId);
     }
 
     @Override
-    public Invoice getInvoice(UUID invoiceId) {
-        return dao.getById(invoiceId.toString());
+    public List<Invoice> getInvoicesByAccount(final UUID accountId, final DateTime fromDate) {
+        return dao.getInvoicesByAccount(accountId, fromDate);
     }
 
     @Override
     public void notifyOfPaymentAttempt(InvoicePayment invoicePayment) {
         dao.notifyOfPaymentAttempt(invoicePayment);
     }
-
-	@Override
+    
+    @Override
 	public BigDecimal getAccountBalance(UUID accountId) {
 		BigDecimal result = dao.getAccountBalance(accountId);
 		return result == null ? BigDecimal.ZERO : result;
 	}
 
+    @Override
+    public List<InvoiceItem> getInvoiceItemsByAccount(final UUID accountId) {
+        return dao.getInvoiceItemsByAccount(accountId);
+    }
+
+    @Override
+    public Invoice getInvoice(final UUID invoiceId) {
+        return dao.getById(invoiceId);
+    }
+
+    @Override
+    public List<Invoice> getUnpaidInvoicesByAccountId(final UUID accountId, final DateTime upToDate) {
+        return dao.getUnpaidInvoicesByAccountId(accountId, upToDate);
+    }
 }
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 35f03c2..22849d1 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,53 +17,92 @@
 package com.ning.billing.invoice.dao;
 
 import java.math.BigDecimal;
-import java.util.Date;
+import java.util.Collection;
 import java.util.List;
 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.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import com.google.inject.Inject;
 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.payment.api.InvoicePayment;
-import com.ning.billing.util.eventbus.EventBus;
+import com.ning.billing.invoice.notification.NextBillingDateNotifier;
+import com.ning.billing.util.bus.Bus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class DefaultInvoiceDao implements InvoiceDao {
-    private final InvoiceSqlDao invoiceDao;
-
-    private final EventBus eventBus;
     private final static Logger log = LoggerFactory.getLogger(DefaultInvoiceDao.class);
 
+    private final InvoiceSqlDao invoiceSqlDao;
+    private final InvoiceItemSqlDao invoiceItemSqlDao;
+    private final InvoicePaymentSqlDao invoicePaymentSqlDao;
+    private final NextBillingDateNotifier notifier;
+    private final EntitlementBillingApi entitlementBillingApi;
+
+    private final Bus eventBus;
+
     @Inject
-    public DefaultInvoiceDao(final IDBI dbi, final EventBus eventBus) {
-        this.invoiceDao = dbi.onDemand(InvoiceSqlDao.class);
+    public DefaultInvoiceDao(final IDBI dbi, final Bus eventBus,
+                             final NextBillingDateNotifier notifier, final EntitlementBillingApi entitlementBillingApi) {
+        this.invoiceSqlDao = dbi.onDemand(InvoiceSqlDao.class);
+        this.invoiceItemSqlDao = dbi.onDemand(InvoiceItemSqlDao.class);
+        this.invoicePaymentSqlDao = dbi.onDemand(InvoicePaymentSqlDao.class);
         this.eventBus = eventBus;
+        this.notifier = notifier;
+        this.entitlementBillingApi = entitlementBillingApi;
     }
 
     @Override
-    public List<Invoice> getInvoicesByAccount(final String accountId) {
-        return invoiceDao.getInvoicesByAccount(accountId);
+    public List<Invoice> getInvoicesByAccount(final UUID accountId) {
+        return invoiceSqlDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
+            @Override
+            public List<Invoice> inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
+                List<Invoice> invoices = invoiceDao.getInvoicesByAccount(accountId.toString());
+
+                getInvoiceItemsWithinTransaction(invoices, invoiceDao);
+                getInvoicePaymentsWithinTransaction(invoices, invoiceDao);
+
+                return invoices;
+            }
+        });
+    }
+
+    @Override
+    public List<Invoice> getInvoicesByAccount(final UUID accountId, final DateTime fromDate) {
+        return invoiceSqlDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
+            @Override
+            public List<Invoice> inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
+                List<Invoice> invoices = invoiceDao.getInvoicesByAccountAfterDate(accountId.toString(), fromDate.toDate());
+
+                getInvoiceItemsWithinTransaction(invoices, invoiceDao);
+                getInvoicePaymentsWithinTransaction(invoices, invoiceDao);
+
+                return invoices;
+            }
+        });
+    }
+
+    @Override
+    public List<InvoiceItem> getInvoiceItemsByAccount(final UUID accountId) {
+        return invoiceItemSqlDao.getInvoiceItemsByAccount(accountId.toString());
     }
 
     @Override
     public List<Invoice> get() {
-        return invoiceDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
+        return invoiceSqlDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
              @Override
              public List<Invoice> inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
                  List<Invoice> invoices = invoiceDao.get();
 
-                 InvoiceItemSqlDao invoiceItemDao = invoiceDao.become(InvoiceItemSqlDao.class);
-                 for (Invoice invoice : invoices) {
-                     List<InvoiceItem> invoiceItems = invoiceItemDao.getInvoiceItemsByInvoice(invoice.getId().toString());
-                     invoice.add(invoiceItems);
-                 }
+                 getInvoiceItemsWithinTransaction(invoices, invoiceDao);
+                 getInvoicePaymentsWithinTransaction(invoices, invoiceDao);
 
                  return invoices;
              }
@@ -71,16 +110,20 @@ public class DefaultInvoiceDao implements InvoiceDao {
     }
 
     @Override
-    public Invoice getById(final String invoiceId) {
-        return invoiceDao.inTransaction(new Transaction<Invoice, InvoiceSqlDao>() {
+    public Invoice getById(final UUID invoiceId) {
+        return invoiceSqlDao.inTransaction(new Transaction<Invoice, InvoiceSqlDao>() {
              @Override
              public Invoice inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
-                 Invoice invoice = invoiceDao.getById(invoiceId);
+                 Invoice invoice = invoiceDao.getById(invoiceId.toString());
 
                  if (invoice != null) {
                      InvoiceItemSqlDao invoiceItemDao = invoiceDao.become(InvoiceItemSqlDao.class);
-                     List<InvoiceItem> invoiceItems = invoiceItemDao.getInvoiceItemsByInvoice(invoiceId);
-                     invoice.add(invoiceItems);
+                     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);
                  }
 
                  return invoice;
@@ -90,76 +133,134 @@ public class DefaultInvoiceDao implements InvoiceDao {
 
     @Override
     public void create(final Invoice invoice) {
-         invoiceDao.inTransaction(new Transaction<Void, InvoiceSqlDao>() {
-             @Override
-             public Void inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
+        invoiceSqlDao.inTransaction(new Transaction<Void, InvoiceSqlDao>() {
+            @Override
+            public Void inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
+
+                // STEPH this seems useless
                 Invoice currentInvoice = invoiceDao.getById(invoice.getId().toString());
 
                 if (currentInvoice == null) {
                     invoiceDao.create(invoice);
 
-                    List<InvoiceItem> invoiceItems = invoice.getItems();
+                    List<InvoiceItem> invoiceItems = invoice.getInvoiceItems();
                     InvoiceItemSqlDao invoiceItemDao = invoiceDao.become(InvoiceItemSqlDao.class);
-                    invoiceItemDao.create(invoiceItems);
+                    invoiceItemDao.batchCreateFromTransaction(invoiceItems);
+
+                    notifyOfFutureBillingEvents(invoiceSqlDao, invoiceItems);
+                    setChargedThroughDates(invoiceSqlDao, invoiceItems);
+
+
+                    // STEPH Why do we need that? Are the payments not always null at this point?
+                    List<InvoicePayment> invoicePayments = invoice.getPayments();
+                    InvoicePaymentSqlDao invoicePaymentSqlDao = invoiceDao.become(InvoicePaymentSqlDao.class);
+                    invoicePaymentSqlDao.batchCreateFromTransaction(invoicePayments);
 
                     InvoiceCreationNotification event;
                     event = new DefaultInvoiceCreationNotification(invoice.getId(), invoice.getAccountId(),
-                                                                  invoice.getAmountOutstanding(), invoice.getCurrency(),
+                                                                  invoice.getBalance(), invoice.getCurrency(),
                                                                   invoice.getInvoiceDate());
-                    eventBus.post(event);
+                    eventBus.postFromTransaction(event, invoiceDao);
                 }
 
                 return null;
-             }
-         });
+            }
+        });
     }
 
     @Override
-    public List<Invoice> getInvoicesBySubscription(final String subscriptionId) {
-        return invoiceDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
-             @Override
-             public List<Invoice> inTransaction(InvoiceSqlDao invoiceDao, TransactionStatus status) throws Exception {
-                 List<Invoice> invoices = invoiceDao.getInvoicesBySubscription(subscriptionId);
+    public List<Invoice> getInvoicesBySubscription(final UUID subscriptionId) {
+        return invoiceSqlDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
+            @Override
+            public List<Invoice> inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
+                List<Invoice> invoices = invoiceDao.getInvoicesBySubscription(subscriptionId.toString());
 
-                 InvoiceItemSqlDao invoiceItemDao = invoiceDao.become(InvoiceItemSqlDao.class);
-                 for (Invoice invoice : invoices) {
-                     List<InvoiceItem> invoiceItems = invoiceItemDao.getInvoiceItemsByInvoice(invoice.getId().toString());
-                     invoice.add(invoiceItems);
-                 }
+                getInvoiceItemsWithinTransaction(invoices, invoiceDao);
+                getInvoicePaymentsWithinTransaction(invoices, invoiceDao);
 
-                 return invoices;
-             }
+                return invoices;
+            }
         });
     }
 
     @Override
-    public List<UUID> getInvoicesForPayment(Date targetDate, int numberOfDays) {
-        return invoiceDao.getInvoicesForPayment(targetDate, numberOfDays);
+    public List<UUID> getInvoicesForPayment(final DateTime targetDate, final int numberOfDays) {
+        return invoiceSqlDao.getInvoicesForPayment(targetDate.toDate(), numberOfDays);
+    }
+
+    @Override
+    public BigDecimal getAccountBalance(final UUID accountId) {
+        return invoiceSqlDao.getAccountBalance(accountId.toString());
     }
 
     @Override
     public void notifyOfPaymentAttempt(InvoicePayment invoicePayment) {
-        invoiceDao.notifyOfPaymentAttempt(invoicePayment);
+        invoicePaymentSqlDao.notifyOfPaymentAttempt(invoicePayment);
     }
 
     @Override
-    public void test() {
-        invoiceDao.test();
+    public List<Invoice> getUnpaidInvoicesByAccountId(final UUID accountId, final DateTime upToDate) {
+        return invoiceSqlDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
+            @Override
+            public List<Invoice> inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
+                List<Invoice> invoices = invoiceSqlDao.getUnpaidInvoicesByAccountId(accountId.toString(), upToDate.toDate());
+
+                getInvoiceItemsWithinTransaction(invoices, invoiceDao);
+                getInvoicePaymentsWithinTransaction(invoices, invoiceDao);
+
+                return invoices;
+            }
+        });
     }
 
     @Override
-    public String getInvoiceIdByPaymentAttemptId(UUID paymentAttemptId) {
-        return invoiceDao.getInvoiceIdByPaymentAttemptId(paymentAttemptId.toString());
+    public UUID getInvoiceIdByPaymentAttemptId(UUID paymentAttemptId) {
+        return invoiceSqlDao.getInvoiceIdByPaymentAttemptId(paymentAttemptId.toString());
     }
 
     @Override
     public InvoicePayment getInvoicePayment(UUID paymentAttemptId) {
-        return invoiceDao.getInvoicePayment(paymentAttemptId);
+        return invoicePaymentSqlDao.getInvoicePayment(paymentAttemptId);
+    }
+
+    @Override
+    public void test() {
+        invoiceSqlDao.test();
     }
 
-	@Override
-	public BigDecimal getAccountBalance(UUID accountId) {
-		return invoiceDao.getAccountBalance(accountId.toString());
-	}
+    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);
+        }
+    }
+
+    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);
+        }
+    }
 
+    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());
+            }
+        }
+    }
+
+    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());
+            }
+        }
+    }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
index 322c286..7a7c280 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
@@ -16,36 +16,42 @@
 
 package com.ning.billing.invoice.dao;
 
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoicePayment;
+import org.joda.time.DateTime;
+
 import java.math.BigDecimal;
-import java.util.Date;
 import java.util.List;
 import java.util.UUID;
 
-import com.ning.billing.invoice.api.Invoice;
-import com.ning.billing.payment.api.InvoicePayment;
-
 public interface InvoiceDao {
     void create(Invoice invoice);
 
-    Invoice getById(final String id);
+    Invoice getById(final UUID id);
 
     List<Invoice> get();
 
-    List<Invoice> getInvoicesByAccount(final String accountId);
+    List<Invoice> getInvoicesByAccount(final UUID accountId);
 
-    List<Invoice> getInvoicesBySubscription(final String subscriptionId);
+    List<Invoice> getInvoicesByAccount(final UUID accountId, final DateTime fromDate);
 
-    List<UUID> getInvoicesForPayment(final Date targetDate,
+    List<InvoiceItem> getInvoiceItemsByAccount(final UUID accountId);
+
+    List<Invoice> getInvoicesBySubscription(final UUID subscriptionId);
+
+    List<UUID> getInvoicesForPayment(final DateTime targetDate,
                                      final int numberOfDays);
 
-    String getInvoiceIdByPaymentAttemptId(UUID paymentAttemptId);
+    UUID getInvoiceIdByPaymentAttemptId(final UUID paymentAttemptId);
 
-    void test();
+    InvoicePayment getInvoicePayment(final UUID paymentAttemptId);
 
-    InvoicePayment getInvoicePayment(UUID paymentAttemptId);
+    void notifyOfPaymentAttempt(final InvoicePayment invoicePayment);
 
-    void notifyOfPaymentAttempt(InvoicePayment invoicePayment);
+    BigDecimal getAccountBalance(final UUID accountId);
 
-    BigDecimal getAccountBalance(UUID accountId);
+    List<Invoice> getUnpaidInvoicesByAccountId(final UUID accountId, final DateTime upToDate);
 
+    void test();
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
index 6f4e47b..a76cd06 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
@@ -19,17 +19,26 @@ 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.DefaultInvoiceItem;
-import com.ning.billing.util.entity.EntityCollectionDao;
 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.*;
+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.*;
+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;
@@ -56,25 +65,29 @@ public interface InvoiceItemSqlDao extends EntityDao<InvoiceItem> {
     @SqlUpdate
     void update(@InvoiceItemBinder final InvoiceItem invoiceItem);
 
-    @SqlBatch
-    void create(@InvoiceItemBinder final List<InvoiceItem> items);
+    @SqlBatch(transactional=false)
+    void batchCreateFromTransaction(@InvoiceItemBinder final List<InvoiceItem> items);
 
     @BindingAnnotation(InvoiceItemBinder.InvoiceItemBinderFactory.class)
     @Retention(RetentionPolicy.RUNTIME)
     @Target({ElementType.PARAMETER})
     public @interface InvoiceItemBinder {
         public static class InvoiceItemBinderFactory implements BinderFactory {
+            @Override
             public Binder build(Annotation annotation) {
                 return new Binder<InvoiceItemBinder, InvoiceItem>() {
+                    @Override
                     public void bind(SQLStatement q, InvoiceItemBinder bind, InvoiceItem 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("description", item.getDescription());
-                        q.bind("amount", item.getAmount());
-                        q.bind("rate", item.getRate());
+                        q.bind("recurringAmount", item.getRecurringAmount());
+                        q.bind("recurringRate", item.getRecurringRate());
+                        q.bind("fixedAmount", item.getFixedAmount());
                         q.bind("currency", item.getCurrency().toString());
                     }
                 };
@@ -88,14 +101,17 @@ public interface InvoiceItemSqlDao extends EntityDao<InvoiceItem> {
             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"));
-            String description = result.getString("description");
-            BigDecimal amount = result.getBigDecimal("amount");
-            BigDecimal rate = result.getBigDecimal("rate");
+            BigDecimal recurringAmount = result.getBigDecimal("recurring_amount");
+            BigDecimal recurringRate = result.getBigDecimal("recurring_rate");
+            BigDecimal fixedAmount = result.getBigDecimal("fixed_amount");
             Currency currency = Currency.valueOf(result.getString("currency"));
 
-            return new DefaultInvoiceItem(id, invoiceId, subscriptionId, startDate, endDate, description, amount, rate , currency);
+            return new DefaultInvoiceItem(id, invoiceId, subscriptionId, planName, phaseName, startDate, endDate,
+                                          recurringAmount, recurringRate, fixedAmount, currency);
         }
     }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java
new file mode 100644
index 0000000..7179ec1
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java
@@ -0,0 +1,147 @@
+/*
+ * 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 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.sql.Timestamp;
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.catalog.api.Currency;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.sqlobject.*;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import com.ning.billing.invoice.api.InvoicePayment;
+
+@ExternalizedSqlViaStringTemplate3
+@RegisterMapper(InvoicePaymentSqlDao.InvoicePaymentMapper.class)
+public interface InvoicePaymentSqlDao {
+    @SqlQuery
+    public InvoicePayment getByPaymentAttemptId(@Bind("paymentAttempt") final String paymentAttemptId);
+
+    @SqlQuery
+    public List<InvoicePayment> get();
+
+    @SqlUpdate
+    public void create(@InvoicePaymentBinder  InvoicePayment invoicePayment);
+
+    @SqlBatch(transactional=false)
+    void batchCreateFromTransaction(@InvoicePaymentBinder List<InvoicePayment> items);
+
+    @SqlUpdate
+    public void update(@InvoicePaymentBinder  InvoicePayment invoicePayment);
+
+    @SqlQuery
+    public List<InvoicePayment> getPaymentsForInvoice(@Bind("invoiceId") String invoiceId);
+
+    @SqlQuery
+    InvoicePayment getInvoicePayment(@Bind("paymentAttemptId") UUID paymentAttemptId);
+
+    @SqlUpdate
+    void notifyOfPaymentAttempt(@InvoicePaymentBinder InvoicePayment invoicePayment);
+
+    public static class InvoicePaymentMapper implements ResultSetMapper<InvoicePayment> {
+        private DateTime getDate(ResultSet rs, String fieldName) throws SQLException {
+            final Timestamp resultStamp = rs.getTimestamp(fieldName);
+            return rs.wasNull() ? null : new DateTime(resultStamp).toDateTime(DateTimeZone.UTC);
+        }
+
+        @Override
+        public InvoicePayment map(int index, ResultSet result, StatementContext context) throws SQLException {
+            final UUID paymentAttemptId = UUID.fromString(result.getString("payment_attempt_id"));
+            final UUID invoiceId = UUID.fromString(result.getString("invoice_id"));
+            final DateTime paymentAttemptDate = getDate(result, "payment_attempt_date");
+            final BigDecimal amount = result.getBigDecimal("amount");
+            final String currencyString = result.getString("currency");
+            final Currency currency = (currencyString == null) ? null : Currency.valueOf(currencyString);
+            final DateTime createdDate = getDate(result, "created_date");
+            final DateTime updatedDate = getDate(result, "updated_date");
+
+            return new InvoicePayment() {
+                private final  DateTime now = new DateTime();
+
+                @Override
+                public UUID getPaymentAttemptId() {
+                    return paymentAttemptId;
+                }
+                @Override
+                public UUID getInvoiceId() {
+                    return invoiceId;
+                }
+                @Override
+                public DateTime getPaymentAttemptDate() {
+                    return paymentAttemptDate;
+                }
+                @Override
+                public BigDecimal getAmount() {
+                    return amount;
+                }
+                @Override
+                public Currency getCurrency() {
+                    return currency;
+                }
+                @Override
+                public DateTime getCreatedDate() {
+                    return createdDate ;
+                }
+                @Override
+                public DateTime getUpdatedDate() {
+                    return updatedDate;
+                }
+            };
+        }
+    }
+
+    @BindingAnnotation(InvoicePaymentBinder.InvoicePaymentBinderFactory.class)
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target({ElementType.PARAMETER})
+    public @interface InvoicePaymentBinder {
+        public static class InvoicePaymentBinderFactory implements BinderFactory {
+            @Override
+            public Binder build(Annotation annotation) {
+                return new Binder<InvoicePaymentBinder, InvoicePayment>() {
+                    @Override
+                    public void bind(SQLStatement q, InvoicePaymentBinder bind, InvoicePayment payment) {
+                        q.bind("invoiceId", payment.getInvoiceId().toString());
+                        q.bind("paymentAttemptId", payment.getPaymentAttemptId().toString());
+                        q.bind("paymentAttemptDate", payment.getPaymentAttemptDate().toDate());
+                        q.bind("amount", payment.getAmount());
+                        Currency currency = payment.getCurrency();
+                        q.bind("currency", (currency == null) ? null : currency.toString());
+                        DateTime createdDate = payment.getCreatedDate();
+                        q.bind("createdDate", (createdDate == null) ? new DateTime().toDate() : createdDate.toDate());
+                        DateTime updatedDate = payment.getUpdatedDate();
+                        q.bind("updatedDate", (updatedDate == null) ? new DateTime().toDate() : updatedDate.toDate());
+                    }
+                };
+            }
+        }
+    }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java
index 1f37102..05be556 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java
@@ -16,22 +16,12 @@
 
 package com.ning.billing.invoice.dao;
 
-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.sql.Timestamp;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.UUID;
-
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.model.DefaultInvoice;
+import com.ning.billing.util.UuidMapper;
+import com.ning.billing.util.entity.EntityDao;
 import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
 import org.skife.jdbi.v2.SQLStatement;
 import org.skife.jdbi.v2.StatementContext;
 import org.skife.jdbi.v2.sqlobject.Bind;
@@ -47,16 +37,20 @@ import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
 import org.skife.jdbi.v2.tweak.ResultSetMapper;
 
-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.model.DefaultInvoice;
-import com.ning.billing.payment.api.InvoicePayment;
-import com.ning.billing.util.UuidMapper;
-import com.ning.billing.util.entity.EntityDao;
+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.Date;
+import java.util.List;
+import java.util.UUID;
 
 @ExternalizedSqlViaStringTemplate3()
-@RegisterMapper({UuidMapper.class, InvoiceSqlDao.InvoiceMapper.class})
+@RegisterMapper(InvoiceSqlDao.InvoiceMapper.class)
 public interface InvoiceSqlDao extends EntityDao<Invoice>, Transactional<InvoiceSqlDao>, Transmogrifier, CloseMe {
     @Override
     @SqlUpdate
@@ -70,43 +64,28 @@ public interface InvoiceSqlDao extends EntityDao<Invoice>, Transactional<Invoice
     List<Invoice> getInvoicesByAccount(@Bind("accountId") final String accountId);
 
     @SqlQuery
-    List<Invoice> getInvoicesBySubscription(@Bind("subscriptionId") final String subscriptionId);
+    List<Invoice> getInvoicesByAccountAfterDate(@Bind("accountId") final String accountId,
+                                                @Bind("fromDate") final Date fromDate);
 
     @SqlQuery
-    String getInvoiceIdByPaymentAttemptId(@Bind("paymentAttemptId") final String paymentAttemptId);
+    List<Invoice> getInvoicesBySubscription(@Bind("subscriptionId") final String subscriptionId);
 
     @SqlQuery
-    List<UUID> getInvoicesForPayment(@Bind("targetDate") final Date targetDate,
-                                     @Bind("numberOfDays") final int numberOfDays);
+    @RegisterMapper(UuidMapper.class)
+    UUID getInvoiceIdByPaymentAttemptId(@Bind("paymentAttemptId") final String paymentAttemptId);
 
     @SqlQuery
-    InvoicePayment getInvoicePayment(@Bind("paymentAttemptId") UUID paymentAttemptId);
+    @RegisterMapper(UuidMapper.class)
+    List<UUID> getInvoicesForPayment(@Bind("targetDate") final Date targetDate,
+                                    @Bind("numberOfDays") final int numberOfDays);
 
-    @SqlUpdate
-    void notifyOfPaymentAttempt(@Bind(binder = InvoicePaymentBinder.class) InvoicePayment invoicePayment);
-    
     @SqlQuery
     @RegisterMapper(BalanceMapper.class)
     BigDecimal getAccountBalance(@Bind("accountId") final String accountId);
 
-    public static class BalanceMapper implements ResultSetMapper<BigDecimal> {
-        @Override
-        public BigDecimal map(final int index, final ResultSet result, final StatementContext context) throws SQLException {
-            BigDecimal amount_invoiced = result.getBigDecimal("amount_invoiced");
-            BigDecimal amount_paid = result.getBigDecimal("amount_paid");
-
-            if (amount_invoiced == null) {
-                amount_invoiced = BigDecimal.ZERO;
-            }
-
-            if (amount_paid == null) {
-                amount_paid = BigDecimal.ZERO;
-            }
-
-            return amount_invoiced.subtract(amount_paid);
-        };
-    }
-
+    @SqlQuery
+    List<Invoice> getUnpaidInvoicesByAccountId(@Bind("accountId") final String accountId,
+                                               @Bind("upToDate") final Date upToDate);
     @BindingAnnotation(InvoiceBinder.InvoiceBinderFactory.class)
     @Retention(RetentionPolicy.RUNTIME)
     @Target({ElementType.PARAMETER})
@@ -122,7 +101,7 @@ public interface InvoiceSqlDao extends EntityDao<Invoice>, Transactional<Invoice
                         q.bind("invoiceDate", invoice.getInvoiceDate().toDate());
                         q.bind("targetDate", invoice.getTargetDate().toDate());
                         q.bind("amountPaid", invoice.getAmountPaid());
-                        q.bind("amountOutstanding", invoice.getAmountOutstanding());
+                        q.bind("amountOutstanding", invoice.getBalance());
                         DateTime last_payment_date = invoice.getLastPaymentAttempt();
                         q.bind("lastPaymentAttempt", last_payment_date == null ? null : last_payment_date.toDate());
                         q.bind("currency", invoice.getCurrency().toString());
@@ -139,51 +118,27 @@ public interface InvoiceSqlDao extends EntityDao<Invoice>, Transactional<Invoice
             UUID accountId = UUID.fromString(result.getString("account_id"));
             DateTime invoiceDate = new DateTime(result.getTimestamp("invoice_date"));
             DateTime targetDate = new DateTime(result.getTimestamp("target_date"));
-            BigDecimal amountPaid = result.getBigDecimal("amount_paid");
-            if (amountPaid == null) {
-                amountPaid = BigDecimal.ZERO;
-            }
-            Timestamp lastPaymentAttemptTimeStamp = result.getTimestamp("last_payment_attempt");
-            DateTime lastPaymentAttempt = lastPaymentAttemptTimeStamp == null ? null : new DateTime(lastPaymentAttemptTimeStamp);
             Currency currency = Currency.valueOf(result.getString("currency"));
 
-            return new DefaultInvoice(id, accountId, invoiceDate, targetDate, currency, lastPaymentAttempt, amountPaid, new ArrayList<InvoiceItem>());
+            return new DefaultInvoice(id, accountId, invoiceDate, targetDate, currency);
         }
     }
 
-    @SqlUpdate
-    void notifyFailedPayment(@Bind(binder = InvoicePaymentBinder.class) InvoicePayment invoicePayment);
-
-    public static final class InvoicePaymentBinder implements Binder<Bind, InvoicePayment> {
-
+    public static class BalanceMapper implements ResultSetMapper<BigDecimal> {
         @Override
-        public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, InvoicePayment invoicePayment) {
-            stmt.bind("invoice_id", invoicePayment.getInvoiceId().toString());
-            stmt.bind("amount", invoicePayment.getAmount());
-            stmt.bind("currency", invoicePayment.getCurrency().toString());
-            stmt.bind("payment_attempt_id", invoicePayment.getPaymentAttemptId().toString());
-            stmt.bind("payment_attempt_date", invoicePayment.getPaymentAttemptDate() == null ? null : invoicePayment.getPaymentAttemptDate().toDate());
-        }
-    }
-
-
-    public static class InvoicePaymentMapper implements ResultSetMapper<InvoicePayment> {
-
-        private DateTime getDate(ResultSet rs, String fieldName) throws SQLException {
-            final Timestamp resultStamp = rs.getTimestamp(fieldName);
-            return rs.wasNull() ? null : new DateTime(resultStamp).toDateTime(DateTimeZone.UTC);
-        }
+        public BigDecimal map(final int index, final ResultSet result, final StatementContext context) throws SQLException {
+            BigDecimal amountInvoiced = result.getBigDecimal("amount_invoiced");
+            BigDecimal amountPaid = result.getBigDecimal("amount_paid");
 
-        @Override
-        public InvoicePayment map(int index, ResultSet rs, StatementContext ctx) throws SQLException {
+            if (amountInvoiced == null) {
+                amountInvoiced = BigDecimal.ZERO;
+            }
 
-            UUID invoiceId = UUID.fromString(rs.getString("invoice_id"));
-            BigDecimal amount = rs.getBigDecimal("amount");
-            Currency currency = Currency.valueOf(rs.getString("currency"));
-            UUID paymentAttemptId = UUID.fromString(rs.getString("payment_attempt_id"));
-            DateTime paymentAttemptDate = getDate(rs, "payment_attempt_date");
+            if (amountPaid == null) {
+                amountPaid = BigDecimal.ZERO;
+            }
 
-            return new InvoicePayment(invoiceId, amount, currency, paymentAttemptId, paymentAttemptDate);
+            return amountInvoiced.subtract(amountPaid);
         }
     }
 
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 63ed8fb..372952b 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
@@ -16,14 +16,26 @@
 
 package com.ning.billing.invoice.glue;
 
+import org.skife.config.ConfigurationObjectFactory;
+
 import com.google.inject.AbstractModule;
+import com.ning.billing.config.InvoiceConfig;
+import com.ning.billing.invoice.InvoiceListener;
+import com.ning.billing.invoice.api.DefaultInvoiceService;
 import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.invoice.api.InvoiceService;
 import com.ning.billing.invoice.api.InvoiceUserApi;
 import com.ning.billing.invoice.api.invoice.DefaultInvoicePaymentApi;
 import com.ning.billing.invoice.api.user.DefaultInvoiceUserApi;
 import com.ning.billing.invoice.dao.DefaultInvoiceDao;
 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.NextBillingDateNotifier;
 import com.ning.billing.util.glue.ClockModule;
+import com.ning.billing.util.glue.GlobalLockerModule;
+
 
 public class InvoiceModule extends AbstractModule {
     protected void installInvoiceDao() {
@@ -37,15 +49,42 @@ public class InvoiceModule extends AbstractModule {
     protected void installInvoicePaymentApi() {
         bind(InvoicePaymentApi.class).to(DefaultInvoicePaymentApi.class).asEagerSingleton();
     }
-    
+
     protected void installClock() {
     	install(new ClockModule());
     }
 
+    protected void installConfig() {
+        final InvoiceConfig config = new ConfigurationObjectFactory(System.getProperties()).build(InvoiceConfig.class);
+        bind(InvoiceConfig.class).toInstance(config);
+    }
+
+    protected void installInvoiceService() {
+        bind(InvoiceService.class).to(DefaultInvoiceService.class).asEagerSingleton();
+    }
+
+    protected void installNotifier() {
+        bind(NextBillingDateNotifier.class).to(DefaultNextBillingDateNotifier.class).asEagerSingleton();
+    }
+
+    protected void installGlobalLocker() {
+        install(new GlobalLockerModule());
+    }
+
+    protected void installInvoiceListener() {
+        bind(InvoiceListener.class).asEagerSingleton();
+    }
+
     @Override
     protected void configure() {
+        installInvoiceService();
+        installNotifier();
+        installInvoiceListener();
+        bind(InvoiceGenerator.class).to(DefaultInvoiceGenerator.class).asEagerSingleton();
+        installConfig();
         installInvoiceDao();
         installInvoiceUserApi();
         installInvoicePaymentApi();
+        installGlobalLocker();
     }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java b/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
new file mode 100644
index 0000000..a86e063
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
@@ -0,0 +1,181 @@
+/*
+ * 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.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;
+
+    @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;
+    }
+
+    @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();
+            }
+        }
+    }
+
+    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);
+            }
+        }
+    }
+
+    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 361a151..0ce3b52 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
@@ -19,8 +19,18 @@ package com.ning.billing.invoice.model;
 import com.ning.billing.entitlement.api.billing.BillingEvent;
 
 import java.util.ArrayList;
+import java.util.Collection;
 
 public class BillingEventSet extends ArrayList<BillingEvent> {
+    public BillingEventSet() {
+        super();
+    }
+
+    public BillingEventSet(Collection<BillingEvent> events) {
+        super();
+        addAll(events);
+    }
+
     public BillingEvent getLast() {
         if (this.size() == 0) {return null;}
 
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 949f711..ffdf806 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
@@ -27,6 +27,10 @@ public abstract class BillingModeBase implements BillingMode {
         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);
@@ -56,10 +60,6 @@ public abstract class BillingModeBase implements BillingMode {
         return precedingProRation.add(numberOfBillingPeriods);
     }
 
-    DateTime buildDate(final int year, final int month, final int day) {
-        return new DateTime(year, month, day, 0, 0, 0, 0);
-    }
-
     boolean isNotBetween(DateTime targetDate, DateTime startDate, DateTime endDate) {
         return (targetDate.isBefore(startDate) || !targetDate.isBefore(endDate));
     }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/DateRange.java b/invoice/src/main/java/com/ning/billing/invoice/model/DateRange.java
index 9f21ca6..b1e2fa9 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/DateRange.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DateRange.java
@@ -33,7 +33,11 @@ public class DateRange {
      * @return whether the DateRange contains (inclusively) the DateTime in question
      */
     public boolean contains(DateTime date) {
-        return (!date.isBefore(startDate)) && (!date.isAfter(endDate));
+        if (endDate == null) {
+            return date.compareTo(startDate) >= 0;
+        }
+
+        return !date.isBefore(startDate) && !date.isAfter(endDate);
     }
 
     public boolean overlaps(DateRange range) {
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 17926f7..8ee40e3 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
@@ -16,6 +16,13 @@
 
 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.InvoiceItem;
+import com.ning.billing.invoice.api.InvoicePayment;
+import com.ning.billing.util.clock.DefaultClock;
+import org.joda.time.DateTime;
+
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.List;
@@ -29,55 +36,65 @@ import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.util.clock.DefaultClock;
 
 public class DefaultInvoice implements Invoice {
-    private final InvoiceItemList items = new InvoiceItemList();
+    private final InvoiceItemList invoiceItems = new InvoiceItemList();
+    private final List<InvoicePayment> payments = new ArrayList<InvoicePayment>();
     private final UUID id;
     private final UUID accountId;
     private final DateTime invoiceDate;
     private final DateTime targetDate;
     private final Currency currency;
-    private final BigDecimal amountPaid;
-    private final DateTime lastPaymentAttempt;
 
     public DefaultInvoice(UUID accountId, DateTime targetDate, Currency currency) {
-        this(UUID.randomUUID(), accountId, new DefaultClock().getUTCNow(), targetDate, currency, null, BigDecimal.ZERO, new ArrayList<InvoiceItem>());
+        this(UUID.randomUUID(), accountId, new DefaultClock().getUTCNow(), targetDate, currency);
     }
 
     public DefaultInvoice(UUID invoiceId, UUID accountId, DateTime invoiceDate, DateTime targetDate,
-                          Currency currency, DateTime lastPaymentAttempt, BigDecimal amountPaid) {
-        this(invoiceId, accountId, invoiceDate, targetDate, currency, lastPaymentAttempt, amountPaid, new ArrayList<InvoiceItem>());
-    }
-
-    public DefaultInvoice(UUID invoiceId, UUID accountId, DateTime invoiceDate, DateTime targetDate,
-                          Currency currency, DateTime lastPaymentAttempt, BigDecimal amountPaid,
-                          List<InvoiceItem> invoiceItems) {
+                          Currency currency) {
         this.id = invoiceId;
         this.accountId = accountId;
         this.invoiceDate = invoiceDate;
         this.targetDate = targetDate;
         this.currency = currency;
-        this.lastPaymentAttempt= lastPaymentAttempt;
-        this.amountPaid = amountPaid;
-        this.items.addAll(invoiceItems);
     }
 
     @Override
-    public boolean add(InvoiceItem item) {
-        return items.add(item);
+    public boolean addInvoiceItem(final InvoiceItem item) {
+        return invoiceItems.add(item);
     }
 
     @Override
-    public boolean add(List<InvoiceItem> items) {
-        return this.items.addAll(items);
+    public boolean addInvoiceItems(final List<InvoiceItem> items) {
+        return this.invoiceItems.addAll(items);
     }
 
     @Override
-    public List<InvoiceItem> getItems() {
-        return items;
+    public List<InvoiceItem> getInvoiceItems() {
+        return invoiceItems;
     }
 
     @Override
     public int getNumberOfItems() {
-        return items.size();
+        return invoiceItems.size();
+    }
+
+    @Override
+    public boolean addPayment(final InvoicePayment payment) {
+        return payments.add(payment);
+    }
+
+    @Override
+    public boolean addPayments(final List<InvoicePayment> payments) {
+        return this.payments.addAll(payments);
+    }
+
+    @Override
+    public List<InvoicePayment> getPayments() {
+        return payments;
+    }
+
+    @Override
+    public int getNumberOfPayments() {
+        return payments.size();
     }
 
     @Override
@@ -107,21 +124,40 @@ public class DefaultInvoice implements Invoice {
 
     @Override
     public DateTime getLastPaymentAttempt() {
+        DateTime lastPaymentAttempt = null;
+
+        for (final InvoicePayment paymentAttempt : payments) {
+            DateTime paymentAttemptDate = paymentAttempt.getPaymentAttemptDate();
+            if (lastPaymentAttempt == null) {
+                lastPaymentAttempt = paymentAttemptDate;
+            }
+
+            if (lastPaymentAttempt.isBefore(paymentAttemptDate)) {
+                lastPaymentAttempt = paymentAttemptDate;
+            }
+        }
+
         return lastPaymentAttempt;
     }
 
     @Override
     public BigDecimal getAmountPaid() {
+        BigDecimal amountPaid = BigDecimal.ZERO;
+        for (final InvoicePayment payment : payments) {
+            if (payment.getAmount() != null) {
+                amountPaid = amountPaid.add(payment.getAmount());
+            }
+        }
         return amountPaid;
     }
 
     @Override
     public BigDecimal getTotalAmount() {
-        return items.getTotalAmount();
+        return invoiceItems.getTotalAmount();
     }
 
     @Override
-    public BigDecimal getAmountOutstanding() {
+    public BigDecimal getBalance() {
         return getTotalAmount().subtract(getAmountPaid());
     }
 
@@ -131,6 +167,7 @@ public class DefaultInvoice implements Invoice {
             return false;
         }
 
+        DateTime lastPaymentAttempt = getLastPaymentAttempt();
         if (lastPaymentAttempt == null) {
             return true;
         }
@@ -140,7 +177,7 @@ public class DefaultInvoice implements Invoice {
 
     @Override
     public String toString() {
-        return "DefaultInvoice [items=" + items + ", id=" + id + ", accountId=" + accountId + ", invoiceDate=" + invoiceDate + ", targetDate=" + targetDate + ", currency=" + currency + ", amountPaid=" + amountPaid + ", lastPaymentAttempt=" + lastPaymentAttempt + "]";
+        return "DefaultInvoice [items=" + invoiceItems + ", payments=" + payments + ", id=" + id + ", accountId=" + accountId + ", invoiceDate=" + invoiceDate + ", targetDate=" + targetDate + ", currency=" + currency + ", amountPaid=" + getAmountPaid() + ", lastPaymentAttempt=" + getLastPaymentAttempt() + "]";
     }
 
 }
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 34a63b2..c83be80 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
@@ -16,18 +16,17 @@
 
 package com.ning.billing.invoice.model;
 
-
 import java.math.BigDecimal;
-import java.util.ArrayList;
 import java.util.Collections;
-import java.util.List;
+import java.util.Iterator;
 import java.util.UUID;
-
 import org.joda.time.DateTime;
+import org.joda.time.Days;
+import org.joda.time.Duration;
+import org.joda.time.Period;
 import org.joda.time.format.ISODateTimeFormat;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.CatalogApiException;
 import com.ning.billing.catalog.api.Currency;
@@ -36,61 +35,82 @@ import com.ning.billing.entitlement.api.billing.BillingModeType;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceItem;
 
+import javax.annotation.Nullable;
+
 public class DefaultInvoiceGenerator implements InvoiceGenerator {
-    private static final Logger log = LoggerFactory.getLogger(DefaultInvoiceGenerator.class); 
+    private static final Logger log = LoggerFactory.getLogger(DefaultInvoiceGenerator.class);
+
     @Override
-    public Invoice generateInvoice(final UUID accountId, final BillingEventSet events, final InvoiceItemList existingItems, final DateTime targetDate, final Currency targetCurrency) {
-        if (events == null) {return new DefaultInvoice(accountId, targetDate, targetCurrency);}
-        if (events.size() == 0) {return new DefaultInvoice(accountId, targetDate, targetCurrency);}
+    public Invoice generateInvoice(final UUID accountId, final BillingEventSet events,
+                                   @Nullable final InvoiceItemList existingItems, final DateTime targetDate,
+                                   final Currency targetCurrency) {
+        if (events == null) {
+            return null;
+        }
+
+        if (events.size() == 0) {
+            return null;
+        }
 
         DefaultInvoice invoice = new DefaultInvoice(accountId, targetDate, targetCurrency);
         InvoiceItemList currentItems = generateInvoiceItems(events, invoice.getId(), targetDate, targetCurrency);
         InvoiceItemList itemsToPost = reconcileInvoiceItems(invoice.getId(), currentItems, existingItems);
-        invoice.add(itemsToPost);
 
-        return invoice;
+        if (itemsToPost.size() == 0) {
+            return null;
+        } else {
+            invoice.addInvoiceItems(itemsToPost);
+            return invoice;
+        }
     }
 
-    private InvoiceItemList reconcileInvoiceItems(final UUID invoiceId, final InvoiceItemList currentInvoiceItems, final InvoiceItemList existingInvoiceItems) {
+    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 (InvoiceItem item : currentInvoiceItems) {
+        for (final InvoiceItem item : currentInvoiceItems) {
             currentItems.add(new DefaultInvoiceItem(item, invoiceId));
         }
 
+        // STEPH why clone? Why cast?
         InvoiceItemList existingItems = (InvoiceItemList) existingInvoiceItems.clone();
 
         Collections.sort(currentItems);
         Collections.sort(existingItems);
 
-        List<InvoiceItem> existingItemsToRemove = new ArrayList<InvoiceItem>();
+        for (final InvoiceItem currentItem : currentItems) {
+            Iterator<InvoiceItem> it = existingItems.iterator();
 
-        for (InvoiceItem currentItem : currentItems) {
             // see if there are any existing items that are covered by the current item
-            for (InvoiceItem existingItem : existingItems) {
+            while (it.hasNext()) {
+                InvoiceItem existingItem = it.next();
+                // STEPH this is more like 'contained' that 'duplicates'
                 if (currentItem.duplicates(existingItem)) {
                     currentItem.subtract(existingItem);
-                    existingItemsToRemove.add(existingItem);
+                    it.remove();
                 }
             }
         }
 
-        existingItems.removeAll(existingItemsToRemove);
-
         // remove cancelling pairs of invoice items
         existingItems.removeCancellingPairs();
 
-        // remove zero-dollar invoice items
-        currentItems.removeZeroDollarItems();
-
         // add existing items that aren't covered by current items as credit items
-        for (InvoiceItem existingItem : existingItems) {
-            currentItems.add(existingItem.asCredit(invoiceId));
+        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()));
         }
 
+        currentItems.cleanupDuplicatedItems();
+
         return currentItems;
     }
 
-    private InvoiceItemList generateInvoiceItems(BillingEventSet events, UUID invoiceId, DateTime targetDate, Currency targetCurrency) {
+    private InvoiceItemList generateInvoiceItems(final BillingEventSet events, final UUID invoiceId,
+                                                 final DateTime targetDate, final Currency targetCurrency) {
         InvoiceItemList items = new InvoiceItemList();
 
         // sort events; this relies on the sort order being by subscription id then start date
@@ -102,7 +122,8 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
             BillingEvent thisEvent = events.get(i);
             BillingEvent nextEvent = events.get(i + 1);
 
-            if (thisEvent.getSubscription().getId() == nextEvent.getSubscription().getId()) {
+
+            if (thisEvent.getSubscription().getId().equals(nextEvent.getSubscription().getId())) {
                 processEvents(invoiceId, thisEvent, nextEvent, items, targetDate, targetCurrency);
             } else {
                 processEvent(invoiceId, thisEvent, items, targetDate, targetCurrency);
@@ -117,62 +138,101 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
         return items;
     }
 
-    private void processEvent(UUID invoiceId, BillingEvent event, List<InvoiceItem> items, DateTime targetDate, Currency targetCurrency) {
+    private void processEvent(final UUID invoiceId, final BillingEvent event, final InvoiceItemList items,
+                              final DateTime targetDate, final Currency targetCurrency) {
     	try {
-    		//TODO: Jeff getPrice() -> getRecurringPrice()
-    		BigDecimal rate = event.getRecurringPrice(targetCurrency);
-    		BigDecimal invoiceItemAmount = calculateInvoiceItemAmount(event, targetDate, rate);
-    		BillingMode billingMode = getBillingMode(event.getBillingMode());
-    		DateTime billThroughDate = billingMode.calculateEffectiveEndDate(event.getEffectiveDate(), targetDate, event.getBillCycleDay(), event.getBillingPeriod());
+    	    // 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;
 
-    		addInvoiceItem(invoiceId, items, event, billThroughDate, invoiceItemAmount, rate, targetCurrency);
+            // 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) {
-            log.error(String.format("Encountered a catalog error processing invoice %s for billing event on date %s", 
-                    invoiceId.toString(), 
+    	    // 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 void processEvents(UUID invoiceId, BillingEvent firstEvent, BillingEvent secondEvent, List<InvoiceItem> items, DateTime targetDate, Currency targetCurrency) {
-    	//TODO: Jeff getPrice() -> getRecurringPrice()
+    private void processEvents(final UUID invoiceId, final BillingEvent firstEvent, final BillingEvent secondEvent,
+                               final InvoiceItemList items, final DateTime targetDate, final Currency targetCurrency) {
     	try {
-    		BigDecimal rate = firstEvent.getRecurringPrice(targetCurrency);
-    		BigDecimal invoiceItemAmount = calculateInvoiceItemAmount(firstEvent, secondEvent, targetDate, rate);
-    		BillingMode billingMode = getBillingMode(firstEvent.getBillingMode());
-    		DateTime billThroughDate = billingMode.calculateEffectiveEndDate(firstEvent.getEffectiveDate(), secondEvent.getEffectiveDate(), targetDate, firstEvent.getBillCycleDay(), firstEvent.getBillingPeriod());
+            BigDecimal recurringRate = firstEvent.getRecurringPrice() == null ? null : firstEvent.getRecurringPrice().getPrice(targetCurrency);
+            BigDecimal fixedPrice = firstEvent.getFixedPrice() == null ? null : firstEvent.getFixedPrice().getPrice(targetCurrency);
 
-    		addInvoiceItem(invoiceId, items, firstEvent, billThroughDate, invoiceItemAmount, rate, targetCurrency);
+            BigDecimal numberOfBillingPeriods;
+            BigDecimal recurringAmount = null;
+            DateTime billThroughDate;
+
+            if (recurringRate == null) {
+                // since it's fixed price only, the following event dictates the end date, regardless of when it takes place
+                billThroughDate = secondEvent.getEffectiveDate();
+            } else {
+                numberOfBillingPeriods = calculateNumberOfBillingPeriods(firstEvent, secondEvent, targetDate);
+                recurringAmount = numberOfBillingPeriods.multiply(recurringRate);
+                BillingMode billingMode = getBillingMode(firstEvent.getBillingMode());
+                billThroughDate = billingMode.calculateEffectiveEndDate(firstEvent.getEffectiveDate(), secondEvent.getEffectiveDate(), targetDate, firstEvent.getBillCycleDay(), firstEvent.getBillingPeriod());
+            }
+
+            if (Days.daysBetween(firstEvent.getEffectiveDate(), billThroughDate).getDays() > 0) {
+                BigDecimal effectiveFixedPrice = items.hasInvoiceItemForPhase(firstEvent.getPlanPhase().getName()) ? null : fixedPrice;
+                addInvoiceItem(invoiceId, items, firstEvent, billThroughDate, recurringAmount, recurringRate, effectiveFixedPrice, targetCurrency);
+            }
     	} catch (CatalogApiException e) {
-    		log.error(String.format("Encountered a catalog error processing invoice %s for billing event on date %s", 
-                    invoiceId.toString(), 
+
+    	    // 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(UUID invoiceId, List<InvoiceItem> items, BillingEvent event, DateTime billThroughDate, BigDecimal amount, BigDecimal rate, Currency currency) {
-        if (!(amount.compareTo(BigDecimal.ZERO) == 0)) {
-            DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, event.getSubscription().getId(), event.getEffectiveDate(), billThroughDate, event.getDescription(), amount, rate, currency);
-            items.add(item);
-        }
+    private void addInvoiceItem(final UUID invoiceId, final InvoiceItemList items, final BillingEvent event,
+                                final DateTime billThroughDate, final BigDecimal amount, final BigDecimal rate,
+                                final BigDecimal fixedAmount, final Currency currency) {
+        DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, event.getSubscription().getId(),
+                                  event.getPlan().getName(), event.getPlanPhase().getName(), event.getEffectiveDate(),
+                                  billThroughDate, amount, rate, fixedAmount, currency);
+        items.add(item);
+        System.out.println(item);
     }
 
-    private BigDecimal calculateInvoiceItemAmount(BillingEvent event, DateTime targetDate, BigDecimal rate){
+    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 {
-            BigDecimal numberOfBillingCycles;
-            numberOfBillingCycles = billingMode.calculateNumberOfBillingCycles(startDate, targetDate, billingCycleDay, billingPeriod);
-            return numberOfBillingCycles.multiply(rate);
+            return billingMode.calculateNumberOfBillingCycles(startDate, targetDate, billingCycleDay, billingPeriod);
         } catch (InvalidDateSequenceException e) {
             // TODO: Jeff -- log issue
             return BigDecimal.ZERO;
         }
     }
 
-    private BigDecimal calculateInvoiceItemAmount(BillingEvent firstEvent, BillingEvent secondEvent, DateTime targetDate, BigDecimal rate) {
+    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();
@@ -181,16 +241,14 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
         DateTime endDate = secondEvent.getEffectiveDate();
 
         try {
-            BigDecimal numberOfBillingCycles;
-            numberOfBillingCycles = billingMode.calculateNumberOfBillingCycles(startDate, endDate, targetDate, billingCycleDay, billingPeriod);
-            return numberOfBillingCycles.multiply(rate);
+            return billingMode.calculateNumberOfBillingCycles(startDate, endDate, targetDate, billingCycleDay, billingPeriod);
         } catch (InvalidDateSequenceException e) {
             // TODO: Jeff -- log issue
             return BigDecimal.ZERO;
         }
     }
 
-    private BillingMode getBillingMode(BillingModeType billingModeType) {
+    private BillingMode getBillingMode(final BillingModeType billingModeType) {
         switch (billingModeType) {
             case IN_ADVANCE:
                 return new InAdvanceBillingMode();
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceItem.java
index eb54aeb..1c1cdef 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceItem.java
@@ -17,9 +17,12 @@
 package com.ning.billing.invoice.model;
 
 import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition;
 import com.ning.billing.invoice.api.InvoiceItem;
 import org.joda.time.DateTime;
 
+import javax.annotation.Nullable;
 import java.math.BigDecimal;
 import java.util.UUID;
 
@@ -27,26 +30,37 @@ public class DefaultInvoiceItem implements InvoiceItem {
     private final UUID id;
     private final UUID invoiceId;
     private final UUID subscriptionId;
+    private final String planName;
+    private final String phaseName;
     private DateTime startDate;
     private DateTime endDate;
-    private final String description;
-    private BigDecimal amount;
-    private final BigDecimal rate;
+    private BigDecimal recurringAmount;
+    private final BigDecimal recurringRate;
+    private final BigDecimal fixedAmount;
     private final Currency currency;
 
-    public DefaultInvoiceItem(UUID invoiceId, UUID subscriptionId, DateTime startDate, DateTime endDate, String description, BigDecimal amount, BigDecimal rate, Currency currency) {
-        this(UUID.randomUUID(), invoiceId, subscriptionId, startDate, endDate, description, amount, rate, currency);
+    public DefaultInvoiceItem(UUID invoiceId, UUID subscriptionId, String planName, String phaseName,
+                              DateTime startDate, DateTime endDate,
+                              BigDecimal recurringAmount, BigDecimal recurringRate,
+                              BigDecimal fixedAmount, Currency currency) {
+        this(UUID.randomUUID(), invoiceId, subscriptionId, planName, phaseName, startDate, endDate,
+             recurringAmount, recurringRate, fixedAmount, currency);
     }
 
-    public DefaultInvoiceItem(UUID id, UUID invoiceId, UUID subscriptionId, DateTime startDate, DateTime endDate, String description, BigDecimal amount, BigDecimal rate, Currency currency) {
+    public DefaultInvoiceItem(UUID id, UUID invoiceId, UUID subscriptionId, String planName, String phaseName,
+                              DateTime startDate, DateTime endDate,
+                              BigDecimal recurringAmount, BigDecimal recurringRate,
+                              BigDecimal fixedAmount, Currency currency) {
         this.id = id;
         this.invoiceId = invoiceId;
         this.subscriptionId = subscriptionId;
+        this.planName = planName;
+        this.phaseName = phaseName;
         this.startDate = startDate;
         this.endDate = endDate;
-        this.description = description;
-        this.amount = amount;
-        this.rate = rate;
+        this.recurringAmount = recurringAmount;
+        this.recurringRate = recurringRate;
+        this.fixedAmount = fixedAmount;
         this.currency = currency;
     }
 
@@ -54,17 +68,22 @@ public class DefaultInvoiceItem implements InvoiceItem {
         this.id = UUID.randomUUID();
         this.invoiceId = invoiceId;
         this.subscriptionId = that.getSubscriptionId();
+        this.planName = that.getPlanName();
+        this.phaseName = that.getPhaseName();
         this.startDate = that.getStartDate();
         this.endDate = that.getEndDate();
-        this.description = that.getDescription();
-        this.amount = that.getAmount();
-        this.rate = that.getRate();
+        this.recurringAmount = that.getRecurringAmount();
+        this.recurringRate = that.getRecurringRate();
+        this.fixedAmount = that.getFixedAmount();
         this.currency = that.getCurrency();
     }
 
     @Override
     public InvoiceItem asCredit(UUID invoiceId) {
-        return new DefaultInvoiceItem(invoiceId, subscriptionId, startDate, endDate, description, amount.negate(), rate, currency);
+        BigDecimal recurringAmountNegated = recurringAmount == null ? null : recurringAmount.negate();
+        BigDecimal fixedAmountNegated = fixedAmount == null ? null : fixedAmount.negate();
+        return new DefaultInvoiceItem(invoiceId, subscriptionId, planName, phaseName, startDate, endDate,
+                                      recurringAmountNegated, recurringRate, fixedAmountNegated, currency);
     }
 
     @Override
@@ -83,6 +102,16 @@ public class DefaultInvoiceItem implements InvoiceItem {
     }
 
     @Override
+    public String getPlanName() {
+        return planName;
+    }
+
+    @Override
+    public String getPhaseName() {
+        return phaseName;
+    }
+
+    @Override
     public DateTime getStartDate() {
         return startDate;
     }
@@ -93,18 +122,18 @@ public class DefaultInvoiceItem implements InvoiceItem {
     }
 
     @Override
-    public String getDescription() {
-        return description;
+    public BigDecimal getRecurringAmount() {
+        return recurringAmount;
     }
 
     @Override
-    public BigDecimal getAmount() {
-        return amount;
+    public BigDecimal getRecurringRate() {
+        return recurringRate;
     }
 
     @Override
-    public BigDecimal getRate() {
-        return rate;
+    public BigDecimal getFixedAmount() {
+        return fixedAmount;
     }
 
     @Override
@@ -113,11 +142,21 @@ public class DefaultInvoiceItem implements InvoiceItem {
     }
 
     @Override
-    public int compareTo(InvoiceItem invoiceItem) {
-        int compareSubscriptions = getSubscriptionId().compareTo(invoiceItem.getSubscriptionId());
+    public int compareTo(InvoiceItem that) {
+        int compareSubscriptions = getSubscriptionId().compareTo(that.getSubscriptionId());
 
         if (compareSubscriptions == 0) {
-            return getStartDate().compareTo(invoiceItem.getStartDate());
+            // move null end dates to the end of the set
+            if ((this.endDate != null) && (that.getEndDate() == null)) {
+                return -1;
+            }
+
+            if ((this.endDate == null) && (that.getEndDate() != null)) {
+                return 1;
+            }
+
+            int compareStartDates = getStartDate().compareTo(that.getStartDate());
+            return compareStartDates;
         } else {
             return compareSubscriptions;
         }
@@ -128,44 +167,139 @@ public class DefaultInvoiceItem implements InvoiceItem {
     public void subtract(InvoiceItem that) {
         if (this.startDate.equals(that.getStartDate()) && this.endDate.equals(that.getEndDate())) {
             this.startDate = this.endDate;
-            this.amount = this.amount.subtract(that.getAmount());
+                this.recurringAmount = safeSubtract(this.recurringAmount, that.getRecurringAmount());
         } else {
             if (this.startDate.equals(that.getStartDate())) {
                 this.startDate = that.getEndDate();
-                this.amount = this.amount.subtract(that.getAmount());
+                this.recurringAmount = safeSubtract(this.recurringAmount, that.getRecurringAmount());
             }
 
             if (this.endDate.equals(that.getEndDate())) {
                 this.endDate = that.getStartDate();
-                this.amount = this.amount.subtract(that.getAmount());
+                this.recurringAmount = safeSubtract(this.recurringAmount, that.getRecurringAmount());
+            }
+        }
+    }
+
+    private BigDecimal safeSubtract(BigDecimal minuend, BigDecimal subtrahend) {
+        // minuend - subtrahend == difference
+        if (minuend == null) {
+            if (subtrahend == null) {
+                return BigDecimal.ZERO;
+            } else {
+                return subtrahend.negate();
+            }
+        } else {
+            if (subtrahend == null) {
+                return minuend;
+            } else {
+                return minuend.subtract(subtrahend);
             }
         }
+
     }
 
     @Override
     public boolean duplicates(InvoiceItem that) {
-        if(!this.getSubscriptionId().equals(that.getSubscriptionId())) {return false;}
-        if(!this.getRate().equals(that.getRate())) {return false;}
-        if(!this.getCurrency().equals(that.getCurrency())) {return false;}
+        if (!this.getSubscriptionId().equals(that.getSubscriptionId())) {return false;}
+
+        if (!this.planName.equals(that.getPlanName())) {return false;}
+        if (!this.phaseName.equals(that.getPhaseName())) {return false;}
+
+        if (!compareNullableBigDecimal(this.getRecurringRate(), that.getRecurringRate())) {return false;}
+
+        if (!this.getCurrency().equals(that.getCurrency())) {return false;}
+
+        if ((this.endDate == null) && (that.getEndDate() == null) && (this.startDate.compareTo(that.getStartDate()) == 0)) {
+            return true;
+        }
 
         DateRange thisDateRange = new DateRange(this.getStartDate(), this.getEndDate());
-        return thisDateRange.contains(that.getStartDate()) && thisDateRange.contains(that.getEndDate());
+        return thisDateRange.contains(that.getStartDate()) && (that.getEndDate() == null || thisDateRange.contains(that.getEndDate()));
+    }
+
+    private boolean compareNullableBigDecimal(@Nullable BigDecimal value1, @Nullable BigDecimal value2) {
+        if ((value1 == null) && (value2 != null)) {return false;}
+        if ((value1 != null) && (value2 == null)) {return false;}
+
+        if ((value1 != null) && (value2 != null)) {
+            if (value1.compareTo(value2) != 0) {return false;}
+        }
+
+        return true;
     }
 
     /**
      * indicates whether the supplied item is a cancelling item for this item
-     * @param that
-     * @return
+     * @param that  the InvoiceItem to be examined
+     * @return true if the two invoice items cancel each other out (same subscription, same date range, sum of amounts = 0)
      */
     @Override
     public boolean cancels(InvoiceItem that) {
         if(!this.getSubscriptionId().equals(that.getSubscriptionId())) {return false;}
         if(!this.getEndDate().equals(that.getEndDate())) {return false;}
         if(!this.getStartDate().equals(that.getStartDate())) {return false;}
-        if(!this.getAmount().equals(that.getAmount().negate())) {return false;}
-        if(!this.getRate().equals(that.getRate())) {return false;}
+
+        if (!safeCheckForZeroSum(this.getRecurringAmount(), that.getRecurringAmount())) {return false;}
+
+        if (!safeCheckForEquality(this.getRecurringRate(), that.getRecurringRate())) {return false;}
+
+        if (!safeCheckForZeroSum(this.getFixedAmount(), that.getFixedAmount())) {return false;}
         if(!this.getCurrency().equals(that.getCurrency())) {return false;}
 
         return true;
     }
+
+    private boolean safeCheckForZeroSum(final BigDecimal value1, final BigDecimal value2) {
+        if ((value1 == null) && (value2 == null)) {return true;}
+        if ((value1 == null) ^ (value2 == null)) {return false;}
+        return (value1.add(value2).compareTo(BigDecimal.ZERO) == 0);
+    }
+
+    private boolean safeCheckForEquality(final BigDecimal value1, final BigDecimal value2) {
+        if ((value1 == null) && (value2 == null)) {return true;}
+        if ((value1 == null) ^ (value2 == null)) {return false;}
+        return (value1.compareTo(value2) == 0);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("InvoiceItem = {").append("id = ").append(id.toString()).append(", ");
+        sb.append("invoiceId = ").append(invoiceId.toString()).append(", ");
+        sb.append("subscriptionId = ").append(subscriptionId.toString()).append(", ");
+        sb.append("planName = ").append(planName).append(", ");
+        sb.append("phaseName = ").append(phaseName).append(", ");
+        sb.append("startDate = ").append(startDate.toString()).append(", ");
+        if (endDate != null) {
+            sb.append("endDate = ").append(endDate.toString()).append(", ");
+        } else {
+            sb.append("endDate = null");
+        }
+        sb.append("recurringAmount = ");
+        if (recurringAmount == null) {
+            sb.append("null");
+        } else {
+            sb.append(recurringAmount.toString());
+        }
+        sb.append(", ");
+
+        sb.append("recurringRate = ");
+        if (recurringRate == null) {
+            sb.append("null");
+        } else {
+            sb.append(recurringRate.toString());
+        }
+        sb.append(", ");
+
+        sb.append("fixedAmount = ");
+        if (fixedAmount == null) {
+            sb.append("null");
+        } else {
+            sb.append(fixedAmount.toString());
+        }
+
+        sb.append("}");
+        return sb.toString();
+    }
 }
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 37b5820..b8f5c42 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java
@@ -20,6 +20,7 @@ import com.ning.billing.catalog.api.BillingPeriod;
 import org.joda.time.DateTime;
 import org.joda.time.Days;
 import org.joda.time.Months;
+import org.joda.time.MutableDateTime;
 
 import java.math.BigDecimal;
 
@@ -50,20 +51,23 @@ public class InAdvanceBillingMode extends BillingModeBase {
     protected DateTime calculateBillingCycleDateOnOrAfter(final DateTime date, final int billingCycleDay) {
         int lastDayOfMonth = date.dayOfMonth().getMaximumValue();
 
-        DateTime proposedDate;
+
+        MutableDateTime tmp = date.toMutableDateTime();
         if (billingCycleDay > lastDayOfMonth) {
-            proposedDate = buildDate(date.getYear(), date.getMonthOfYear(), lastDayOfMonth);
+            tmp.setDayOfMonth(lastDayOfMonth);
         } else {
-            proposedDate = buildDate(date.getYear(), date.getMonthOfYear(), billingCycleDay);
+            tmp.setDayOfMonth(billingCycleDay);
         }
+        DateTime proposedDate = tmp.toDateTime();
 
         while (proposedDate.isBefore(date)) {
+            // STEPH could be an annual ?
             proposedDate = proposedDate.plusMonths(1);
         }
-
         return proposedDate;
     }
 
+    @Override
     protected DateTime calculateBillingCycleDateAfter(final DateTime date, final DateTime billingCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod) {
         DateTime proposedDate = billingCycleDate;
 
@@ -74,9 +78,13 @@ public class InAdvanceBillingMode extends BillingModeBase {
                 int lastDayOfMonth = proposedDate.dayOfMonth().getMaximumValue();
 
                 if (lastDayOfMonth < billingCycleDay) {
-                    proposedDate = buildDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), lastDayOfMonth);
+                    proposedDate = new DateTime(proposedDate.getYear(), proposedDate.getMonthOfYear(), lastDayOfMonth,
+                                                proposedDate.getHourOfDay(), proposedDate.getMinuteOfHour(),
+                                                proposedDate.getSecondOfMinute(), proposedDate.getMillisOfSecond());
                 } else {
-                    proposedDate = buildDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), billingCycleDay);
+                    proposedDate = new DateTime(proposedDate.getYear(), proposedDate.getMonthOfYear(), billingCycleDay,
+                                                proposedDate.getHourOfDay(), proposedDate.getMinuteOfHour(),
+                                                proposedDate.getSecondOfMinute(), proposedDate.getMillisOfSecond());
                 }
             }
         }
@@ -100,9 +108,13 @@ public class InAdvanceBillingMode extends BillingModeBase {
         if (proposedDate.dayOfMonth().get() < billingCycleDay) {
             int lastDayOfTheMonth = proposedDate.dayOfMonth().getMaximumValue();
             if (lastDayOfTheMonth < billingCycleDay) {
-                return buildDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), lastDayOfTheMonth);
+                return new DateTime(proposedDate.getYear(), proposedDate.getMonthOfYear(), lastDayOfTheMonth,
+                                    proposedDate.getHourOfDay(), proposedDate.getMinuteOfHour(),
+                                    proposedDate.getSecondOfMinute(), proposedDate.getMillisOfSecond());
             } else {
-                return buildDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), billingCycleDay);
+                return new DateTime(proposedDate.getYear(), proposedDate.getMonthOfYear(), billingCycleDay,
+                                    proposedDate.getHourOfDay(), proposedDate.getMinuteOfHour(),
+                                    proposedDate.getSecondOfMinute(), proposedDate.getMillisOfSecond());
             }
         } else {
             return proposedDate;
@@ -114,7 +126,12 @@ public class InAdvanceBillingMode extends BillingModeBase {
         DateTime nextBillingCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
         DateTime previousBillingCycleDate = nextBillingCycleDate.plusMonths(-billingPeriod.getNumberOfMonths());
 
-        BigDecimal daysInPeriod = new BigDecimal(Days.daysBetween(previousBillingCycleDate, nextBillingCycleDate).getDays());
+        int daysBetween = Days.daysBetween(previousBillingCycleDate, nextBillingCycleDate).getDays();
+        if (daysBetween == 0) {
+            return BigDecimal.ZERO;
+        }
+
+        BigDecimal daysInPeriod = new BigDecimal(daysBetween);
         BigDecimal days = new BigDecimal(Days.daysBetween(startDate, nextBillingCycleDate).getDays());
 
         return days.divide(daysInPeriod, NUMBER_OF_DECIMALS, ROUNDING_METHOD);
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 b4d81a7..626993e 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
@@ -20,8 +20,9 @@ import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.Invoice;
 import org.joda.time.DateTime;
 
+import javax.annotation.Nullable;
 import java.util.UUID;
 
 public interface InvoiceGenerator {
-    public Invoice generateInvoice(UUID accountId, BillingEventSet events, InvoiceItemList items, DateTime targetDate, Currency targetCurrency);
+    public Invoice generateInvoice(UUID accountId, BillingEventSet events, @Nullable InvoiceItemList items, DateTime targetDate, Currency targetCurrency);
 }
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 fb82f06..f35f8b1 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
@@ -16,36 +16,40 @@
 
 package com.ning.billing.invoice.model;
 
-import com.ning.billing.invoice.api.InvoiceItem;
-
 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();
 
-    public BigDecimal getTotalAmount() {
-        // TODO: Jeff -- naive implementation, assumes all invoice items share the same currency
-        BigDecimal total = new BigDecimal("0");
-
-        for (InvoiceItem item : this) {
-            total = total.add(item.getAmount());
-        }
+    public InvoiceItemList() {
+        super();
+    }
 
-        return total.setScale(NUMBER_OF_DECIMALS);
+    public InvoiceItemList(final List<InvoiceItem> invoiceItems) {
+        super();
+        this.addAll(invoiceItems);
     }
 
-    public void removeZeroDollarItems() {
-        List<InvoiceItem> itemsToRemove = new ArrayList<InvoiceItem>();
+    public BigDecimal getTotalAmount() {
+        // naive implementation, assumes all invoice items share the same currency
+        BigDecimal total = BigDecimal.ZERO.setScale(NUMBER_OF_DECIMALS, ROUNDING_METHOD);
 
-        for (InvoiceItem item : this) {
-            if (item.getAmount().compareTo(BigDecimal.ZERO) == 0) {
-                itemsToRemove.add(item);
+        for (final InvoiceItem item : this) {
+            if (item.getRecurringAmount() != null) {
+                total = total.add(item.getRecurringAmount());
+            }
+
+            if (item.getFixedAmount() != null) {
+                total = total.add(item.getFixedAmount());
             }
         }
 
-        this.removeAll(itemsToRemove);
+        return total.setScale(NUMBER_OF_DECIMALS, ROUNDING_METHOD);
     }
 
     public void removeCancellingPairs() {
@@ -64,4 +68,36 @@ public class InvoiceItemList extends ArrayList<InvoiceItem> {
 
         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;
+    }
 }
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
new file mode 100644
index 0000000..3fd03b2
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java
@@ -0,0 +1,144 @@
+/*
+ * 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.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;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+import com.ning.billing.config.InvoiceConfig;
+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;
+import com.ning.billing.util.notificationq.NotificationQueueService;
+import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueAlreadyExists;
+import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
+
+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";
+
+    private final Bus eventBus;
+    private final NotificationQueueService notificationQueueService;
+	private final InvoiceConfig config;
+    private final EntitlementDao entitlementDao;
+
+    private NotificationQueue nextBillingQueue;
+
+    @Inject
+	public DefaultNextBillingDateNotifier(NotificationQueueService notificationQueueService, Bus eventBus,
+                                          InvoiceConfig config, EntitlementDao entitlementDao){
+		this.notificationQueueService = notificationQueueService;
+		this.config = config;
+		this.eventBus = eventBus;
+        this.entitlementDao = entitlementDao;
+	}
+
+    @Override
+    public void initialize() {
+		try {
+            nextBillingQueue = notificationQueueService.createNotificationQueue(DefaultInvoiceService.INVOICE_SERVICE_NAME,
+            		NEXT_BILLING_DATE_NOTIFIER_QUEUE,
+                    new NotificationQueueHandler() {
+                @Override
+                public void handleReadyNotification(String notificationKey) {
+                	UUID subscriptionId;
+                	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);
+                        }
+                	} catch (IllegalArgumentException e) {
+                		log.error("The key returned from the NextBillingNotificationQueue is not a valid UUID", e);
+                		return;
+                	}
+
+                }
+            },
+            new NotificationConfig() {
+                @Override
+                public boolean isNotificationProcessingOff() {
+                    return config.isEventProcessingOff();
+                }
+                @Override
+                public long getNotificationSleepTimeMs() {
+                    return config.getNotificationSleepTimeMs();
+                }
+                @Override
+                public int getDaoMaxReadyEvents() {
+                    return config.getDaoMaxReadyEvents();
+                }
+                @Override
+                public long getDaoClaimTimeMs() {
+                    return config.getDaoMaxReadyEvents();
+                }
+            });
+        } catch (NotificationQueueAlreadyExists e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void start() {
+    	nextBillingQueue.startQueue();
+    }
+
+    @Override
+    public void stop() {
+        if (nextBillingQueue != null) {
+        	nextBillingQueue.stopQueue();
+        }
+    }
+
+    private void processEvent(UUID subscriptionId) {
+        try {
+            eventBus.post(new NextBillingDateEvent(subscriptionId));
+        } catch (EventBusException e) {
+            log.error("Failed to post entitlement event " + subscriptionId, e);
+        }
+    }
+
+    @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/NextBillingDateEvent.java b/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDateEvent.java
new file mode 100644
index 0000000..659d9e9
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDateEvent.java
@@ -0,0 +1,34 @@
+/*
+ * 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.UUID;
+
+import com.ning.billing.util.bus.BusEvent;
+
+public class NextBillingDateEvent implements BusEvent{
+	private final UUID subscriptionId;
+
+	public NextBillingDateEvent(UUID subscriptionId) {
+		super();
+		this.subscriptionId = subscriptionId;
+	}
+
+	public UUID getSubscriptionId() {
+		return subscriptionId;
+	}
+}
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
new file mode 100644
index 0000000..d33dc61
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDateNotifier.java
@@ -0,0 +1,35 @@
+/*
+ * 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.UUID;
+
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+
+public interface NextBillingDateNotifier {
+
+    public void initialize();
+
+    public void start();
+
+    public void stop();
+
+	public void insertNextBillingNotification(Transmogrifier transactionalDao,
+			UUID subscriptionId, DateTime futureNotificationTime);
+
+}
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceItemSqlDao.sql.stg b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
index 4dc783f..451ebe7 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
@@ -1,39 +1,61 @@
 group InvoiceItemDao;
 
+invoiceItemFields(prefix) ::= <<
+  <prefix>id,
+  <prefix>invoice_id,
+  <prefix>subscription_id,
+  <prefix>plan_name,
+  <prefix>phase_name,
+  <prefix>start_date,
+  <prefix>end_date,
+  <prefix>recurring_amount,
+  <prefix>recurring_rate,
+  <prefix>fixed_amount,
+  <prefix>currency
+>>
+
 getById() ::= <<
-  SELECT id, invoice_id, subscription_id, start_date, end_date, description, amount, rate, currency
+  SELECT <invoiceItemFields()>
   FROM invoice_items
   WHERE id = :id;
 >>
 
 getInvoiceItemsByInvoice() ::= <<
-  SELECT id, invoice_id, subscription_id, start_date, end_date, description, amount, rate, currency
+  SELECT <invoiceItemFields()>
   FROM invoice_items
   WHERE invoice_id = :invoiceId;
 >>
 
 getInvoiceItemsByAccount() ::= <<
-  SELECT ii.id, ii.invoice_id, ii.subscription_id, ii.start_date, ii.end_date, ii.description, ii.amount, ii.rate, ii.currency
+  SELECT <invoiceItemFields("ii.")>
   FROM invoice_items ii
   INNER JOIN invoices i ON i.id = ii.invoice_id
   WHERE i.account_id = :accountId;
 >>
 
 getInvoiceItemsBySubscription() ::= <<
-  SELECT id, invoice_id, subscription_id, start_date, end_date, description, amount, rate, currency
+  SELECT <invoiceItemFields()>
   FROM invoice_items
   WHERE subscription_id = :subscriptionId;
 >>
 
 create() ::= <<
-  INSERT INTO invoice_items(id, invoice_id, subscription_id, start_date, end_date, description, amount, rate, currency)
-  VALUES(:id, :invoiceId, :subscriptionId, :startDate, :endDate, :description, :amount, :rate, :currency);
+  INSERT INTO invoice_items(<invoiceItemFields()>)
+  VALUES(:id, :invoiceId, :subscriptionId, :planName, :phaseName, :startDate, :endDate,
+         :recurringAmount, :recurringRate, :fixedAmount, :currency);
+>>
+
+batchCreateFromTransaction() ::= <<
+  INSERT INTO invoice_items(<invoiceItemFields()>)
+  VALUES(:id, :invoiceId, :subscriptionId, :planName, :phaseName, :startDate, :endDate,
+         :recurringAmount, :recurringRate, :fixedAmount, :currency);
 >>
 
 update() ::= <<
   UPDATE invoice_items
-  SET invoice_id = :invoiceId, subscription_id = :subscriptionId, start_date = :startDate, end_date = :endDate,
-      description = :description, amount = :amount, rate = :rate, currency = :currency
+  SET invoice_id = :invoiceId, subscription_id = :subscriptionId, plan_name = :planName, phase_name = :phaseName,
+      start_date = :startDate, end_date = :endDate, recurring_amount = :recurringAmount, recurring_rate = :recurringRate,
+      fixed_amount = :fixedAmount, currency = :currency
   WHERE id = :id;
 >>
 
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
new file mode 100644
index 0000000..2172573
--- /dev/null
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
@@ -0,0 +1,61 @@
+group InvoicePayment;
+
+invoicePaymentFields(prefix) ::= <<
+  <prefix>invoice_id,
+  <prefix>payment_attempt_id,
+  <prefix>payment_attempt_date,
+  <prefix>amount,
+  <prefix>currency,
+  <prefix>created_date,
+  <prefix>updated_date
+>>
+
+create() ::= <<
+  INSERT INTO invoice_payments(<invoicePaymentFields()>)
+  VALUES(:invoiceId, :paymentAttemptId, :paymentAttemptDate, :amount, :currency, :createdDate, :updatedDate);
+>>
+
+batchCreateFromTransaction() ::= <<
+  INSERT INTO invoice_payments(<invoicePaymentFields()>)
+  VALUES(:invoiceId, :paymentAttemptId, :paymentAttemptDate, :amount, :currency, :createdDate, :updatedDate);
+>>
+
+
+update() ::= <<
+  UPDATE invoice_payments
+  SET payment_date = :paymentAttemptDate, amount = :amount, currency = :currency, created_date = :createdDate, updated_date = :updatedDate
+  WHERE invoice_id = :invoiceId, payment_attempt_id = :paymentAttemptId;
+>>
+
+getByPaymentAttemptId() ::= <<
+  SELECT <invoicePaymentFields()>
+  FROM invoice_payments
+  WHERE payment_id = :paymentAttemptId;
+>>
+
+get() ::= <<
+  SELECT <invoicePaymentFields()>
+  FROM invoice_payments;
+>>
+
+getPaymentsForInvoice() ::= <<
+  SELECT <invoicePaymentFields()>
+  FROM invoice_payments
+  WHERE invoice_id = :invoiceId;
+>>
+
+notifyOfPaymentAttempt() ::= <<
+  INSERT INTO invoice_payments(<invoicePaymentFields()>)
+  VALUES(:invoiceId, :paymentAttemptId, :paymentAttemptDate, :amount, :currency, NOW(), NOW());
+>>
+
+getInvoicePayment() ::= <<
+    SELECT <invoicePaymentFields()>
+    FROM invoice_payments
+    WHERE payment_id = :payment_id;
+>>
+
+test() ::= <<
+  SELECT 1 FROM invoice_payments;
+>>
+;
\ 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 5d5725d..10beb12 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
@@ -1,34 +1,39 @@
 group InvoiceDao;
 
+invoiceFields(prefix) ::= <<
+    <prefix>id,
+    <prefix>account_id,
+    <prefix>invoice_date,
+    <prefix>target_date,
+    <prefix>currency
+>>
+
 get() ::= <<
-  SELECT i.id, i.account_id, i.invoice_date, i.target_date, i.currency, SUM(ii.amount) AS amount,
-         SUM(ip.amount) AS amount_paid, MAX(ip.payment_attempt_date) AS last_payment_attempt
-  FROM invoices i
-  LEFT JOIN invoice_payments ip ON ip.invoice_id = i.id
-  LEFT JOIN invoice_items ii ON ii.invoice_id = i.id
-  GROUP BY i.id, i.account_id, i.invoice_date, i.target_date, i.currency
-  ORDER BY i.invoice_date ASC;
+  SELECT <invoiceFields()>
+  FROM invoices
+  ORDER BY target_date ASC;
 >>
 
 getInvoicesByAccount() ::= <<
-  SELECT i.id, i.account_id, i.invoice_date, i.target_date, i.currency, SUM(ii.amount) AS amount,
-         SUM(ip.amount) AS amount_paid, MAX(ip.payment_attempt_date) AS last_payment_attempt
-  FROM invoices i
-  LEFT JOIN invoice_payments ip ON ip.invoice_id = i.id
-  LEFT JOIN invoice_items ii ON ii.invoice_id = i.id
-  WHERE i.account_id = :accountId
-  GROUP BY i.id, i.account_id, i.invoice_date, i.target_date, i.currency
-  ORDER BY i.invoice_date ASC;
+  SELECT <invoiceFields()>
+  FROM invoices
+  WHERE account_id = :accountId
+  ORDER BY target_date ASC;
+>>
+
+getInvoicesByAccountAfterDate() ::= <<
+  SELECT <invoiceFields()>
+  FROM invoices
+  WHERE account_id = :accountId AND target_date >= :fromDate
+  ORDER BY target_date ASC;
 >>
 
 getInvoicesBySubscription() ::= <<
-  SELECT i.id, i.account_id, i.invoice_date, i.target_date, i.currency, SUM(ii.amount) AS amount,
-         SUM(ip.amount) AS amount_paid, MAX(ip.payment_attempt_date) AS last_payment_attempt
+  SELECT <invoiceFields("i.")>
   FROM invoices i
   LEFT JOIN invoice_items ii ON i.id = ii.invoice_id
-  LEFT JOIN invoice_payments ip ON ip.invoice_id = i.id
   WHERE ii.subscription_id = :subscriptionId
-  GROUP BY i.id, i.account_id, i.invoice_date, i.target_date, i.currency;
+  GROUP BY <invoiceFields("i.")>;
 >>
 
 getInvoicesForPayment() ::= <<
@@ -37,23 +42,29 @@ getInvoicesForPayment() ::= <<
   LEFT JOIN invoice_payment_summary ips ON ips.invoice_id = i.id
   LEFT JOIN invoice_item_summary iis ON iis.invoice_id = i.id
   WHERE ((ips.last_payment_date IS NULL) OR (DATEDIFF(:targetDate, ips.last_payment_date) >= :numberOfDays))
-        AND ((ips.total_paid IS NULL) OR (iis.total_amount >= ips.total_paid))
-        AND ((iis.total_amount IS NOT NULL) AND (iis.total_amount > 0))
-  GROUP BY i.id, i.account_id, i.invoice_date, i.target_date, i.currency;
+        AND ((ips.total_paid IS NULL) OR (iis.amount_invoiced >= ips.total_paid))
+        AND ((iis.amount_invoiced IS NOT NULL) AND (iis.amount_invoiced > 0))
+  GROUP BY <invoiceFields("i.")>;
 >>
 
 getById() ::= <<
-  SELECT i.id, i.account_id, i.invoice_date, i.target_date, i.currency, SUM(ii.amount) AS amount,
-         SUM(ip.amount) AS amount_paid, MAX(ip.payment_attempt_date) AS last_payment_attempt
+  SELECT <invoiceFields()>
+  FROM invoices
+  WHERE id = :id;
+>>
+
+getAccountBalance() ::= <<
+  SELECT SUM(iis.amount_invoiced) AS amount_invoiced,
+         SUM(ips.total_paid) AS amount_paid
   FROM invoices i
-  LEFT JOIN invoice_items ii ON i.id = ii.invoice_id
-  LEFT JOIN invoice_payments ip ON ip.invoice_id = i.id
-  WHERE i.id = :id
-  GROUP BY i.id, i.account_id, i.invoice_date, i.target_date, i.currency;
+  LEFT JOIN invoice_payment_summary ips ON i.id = ips.invoice_id
+  LEFT JOIN invoice_item_summary iis ON i.id = iis.invoice_id
+  WHERE i.account_id = :accountId
+  GROUP BY i.account_id;
 >>
 
 create() ::= <<
-  INSERT INTO invoices(id, account_id, invoice_date, target_date, currency)
+  INSERT INTO invoices(<invoiceFields()>)
   VALUES (:id, :accountId, :invoiceDate, :targetDate, :currency);
 >>
 
@@ -70,26 +81,18 @@ update() ::= <<
   WHERE id = :id;
 >>
 
-notifyOfPaymentAttempt() ::= <<
-  INSERT INTO invoice_payments(invoice_id, payment_attempt_id, payment_attempt_date, amount, currency, created_date, updated_date)
-  VALUES(:invoice_id, :payment_attempt_id, :payment_attempt_date, :amount, :currency, NOW(), NOW());
->>
-
-getInvoicePayment() ::= <<
-    SELECT invoice_id, payment_attempt_id, payment_attempt_date, amount, currency, created_date, updated_date
-      FROM invoice_payments
-     WHERE payment_id = :payment_id
->>
-
-getAccountBalance() ::= <<
-  SELECT SUM(iis.total_amount) AS amount_invoiced, SUM(ips.total_paid) AS amount_paid
+getUnpaidInvoicesByAccountId() ::= <<
+  SELECT i.id, i.account_id, i.invoice_date, i.target_date, i.currency
   FROM invoices i
   LEFT JOIN invoice_payment_summary ips ON i.id = ips.invoice_id
   LEFT JOIN invoice_item_summary iis ON i.id = iis.invoice_id
-  WHERE i.account_id = :accountId
-  GROUP BY i.account_id;
+  WHERE i.account_id = :accountId AND NOT (i.target_date > :upToDate)
+  GROUP BY i.id, i.account_id, i.invoice_date, i.target_date, i.currency
+  HAVING (SUM(iis.amount_invoiced) > SUM(ips.total_paid)) OR (SUM(ips.total_paid) IS NULL)
+  ORDER BY i.target_date ASC;
 >>
 
+
 test() ::= <<
   SELECT 1
   FROM invoices;
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 651e9d4..28f3c89 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
+++ b/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
@@ -3,16 +3,24 @@ CREATE TABLE 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,
-  description varchar(100) NOT NULL,
-  amount numeric(10,4) NOT NULL,
-  rate numeric(10,4) NOT NULL,
+  recurring_amount numeric(10,4) NULL,
+  recurring_rate numeric(10,4) NULL,
+  fixed_amount numeric(10,4) NULL,
   currency char(3) NOT NULL,
   PRIMARY KEY(id)
 ) ENGINE=innodb;
 CREATE INDEX invoice_items_subscription_id ON invoice_items(subscription_id ASC);
 
+DROP TABLE IF EXISTS invoice_locking;
+CREATE TABLE invoice_locking (
+  account_id char(36) NOT NULL,
+  PRIMARY KEY(account_id)
+) ENGINE = innodb;
+
 DROP TABLE IF EXISTS invoices;
 CREATE TABLE invoices (
   id char(36) NOT NULL,
@@ -39,12 +47,16 @@ CREATE UNIQUE INDEX invoice_payments_unique ON invoice_payments(invoice_id, paym
 
 DROP VIEW IF EXISTS invoice_payment_summary;
 CREATE VIEW invoice_payment_summary AS
-SELECT invoice_id, SUM(amount) AS total_paid, MAX(payment_attempt_date) AS last_payment_date
+SELECT invoice_id,
+       CASE WHEN SUM(amount) IS NULL THEN 0 ELSE SUM(amount) END AS total_paid,
+       MAX(payment_attempt_date) AS last_payment_date
 FROM invoice_payments
 GROUP BY invoice_id;
 
 DROP VIEW IF EXISTS invoice_item_summary;
 CREATE VIEW invoice_item_summary AS
-SELECT invoice_id, SUM(amount) AS total_amount
+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
-GROUP BY invoice_id;
\ No newline at end of file
+GROUP BY invoice_id;
diff --git a/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java b/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java
index 37d5f0c..e8f66b1 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java
@@ -22,10 +22,10 @@ import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.CopyOnWriteArrayList;
 
+import com.ning.billing.invoice.model.DefaultInvoicePayment;
 import org.joda.time.DateTime;
 
 import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.payment.api.InvoicePayment;
 
 public class MockInvoicePaymentApi implements InvoicePaymentApi
 {
@@ -90,13 +90,13 @@ public class MockInvoicePaymentApi implements InvoicePaymentApi
 
     @Override
     public void notifyOfPaymentAttempt(UUID invoiceId, BigDecimal amountOutstanding, Currency currency, UUID paymentAttemptId, DateTime paymentAttemptDate) {
-        InvoicePayment invoicePayment = new InvoicePayment(invoiceId, amountOutstanding, currency, paymentAttemptId, paymentAttemptDate);
+        InvoicePayment invoicePayment = new DefaultInvoicePayment(paymentAttemptId, invoiceId, paymentAttemptDate, amountOutstanding, currency);
         notifyOfPaymentAttempt(invoicePayment);
     }
 
     @Override
     public void notifyOfPaymentAttempt(UUID invoiceId, UUID paymentAttemptId, DateTime paymentAttemptDate) {
-        InvoicePayment invoicePayment = new InvoicePayment(invoiceId, null, null, paymentAttemptId, paymentAttemptDate);
+        InvoicePayment invoicePayment = new DefaultInvoicePayment(paymentAttemptId, invoiceId, paymentAttemptDate);
         notifyOfPaymentAttempt(invoicePayment);
     }
 }
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 ed5497f..4779495 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
@@ -16,10 +16,12 @@
 
 package com.ning.billing.invoice.dao;
 
+import static org.testng.Assert.assertTrue;
 import static org.testng.Assert.fail;
 
 import java.io.IOException;
 
+import com.ning.billing.invoice.tests.InvoicingTestBase;
 import org.apache.commons.io.IOUtils;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
@@ -28,32 +30,40 @@ import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
 import com.ning.billing.invoice.glue.InvoiceModuleWithEmbeddedDb;
-import com.ning.billing.util.eventbus.DefaultEventBusService;
-import com.ning.billing.util.eventbus.EventBusService;
+import com.ning.billing.util.bus.BusService;
+import com.ning.billing.util.bus.DefaultBusService;
 
-public abstract class InvoiceDaoTestBase {
+public abstract class InvoiceDaoTestBase extends InvoicingTestBase {
     protected InvoiceDao invoiceDao;
     protected InvoiceItemSqlDao invoiceItemDao;
-    private InvoiceModuleWithEmbeddedDb module;
+    protected InvoicePaymentSqlDao invoicePaymentDao;
+    protected InvoiceModuleWithEmbeddedDb module;
 
-    @BeforeClass()
+    @BeforeClass(alwaysRun = true)
     protected void setup() throws IOException {
         // Health check test to make sure MySQL is setup properly
         try {
-
             module = new InvoiceModuleWithEmbeddedDb();
-            final String ddl = IOUtils.toString(DefaultInvoiceDao.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
-            module.createDb(ddl);
+            final String invoiceDdl = IOUtils.toString(DefaultInvoiceDao.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
+            final String entitlementDdl = IOUtils.toString(DefaultInvoiceDao.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
+
+            module.startDb();
+            module.initDb(invoiceDdl);
+            module.initDb(entitlementDdl);
 
             final Injector injector = Guice.createInjector(Stage.DEVELOPMENT, module);
 
             invoiceDao = injector.getInstance(InvoiceDao.class);
             invoiceDao.test();
 
-            invoiceItemDao = module.getInvoiceItemDao();
+            invoiceItemDao = module.getInvoiceItemSqlDao();
+
+            invoicePaymentDao = module.getInvoicePaymentSqlDao();
+
+            BusService busService = injector.getInstance(BusService.class);
+            ((DefaultBusService) busService).startBus();
 
-            EventBusService busService = injector.getInstance(EventBusService.class);
-            ((DefaultEventBusService) busService).startBus();
+            assertTrue(true);
         }
         catch (Throwable t) {
             fail(t.toString());
@@ -61,8 +71,8 @@ public abstract class InvoiceDaoTestBase {
     }
 
     @AfterClass(alwaysRun = true)
-    public void stopMysql()
-    {
+    protected void tearDown() {
         module.stopDb();
+        assertTrue(true);
     }
 }
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 859e09b..2369467 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
@@ -16,31 +16,46 @@
 
 package com.ning.billing.invoice.dao;
 
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
-import static org.testng.Assert.assertTrue;
-
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
-import org.testng.annotations.Test;
-
+import com.ning.billing.catalog.DefaultPrice;
+import com.ning.billing.catalog.MockInternationalPrice;
+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.PhaseType;
+import com.ning.billing.entitlement.api.billing.BillingEvent;
+import com.ning.billing.entitlement.api.billing.BillingModeType;
+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.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.payment.api.InvoicePayment;
+import com.ning.billing.invoice.model.DefaultInvoicePayment;
+import com.ning.billing.invoice.model.InvoiceGenerator;
+import com.ning.billing.invoice.model.InvoiceItemList;
+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 java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
+
+import static org.testng.Assert.*;
+import static org.testng.Assert.assertEquals;
 
 @Test(groups = {"invoicing", "invoicing-invoiceDao"})
 public class InvoiceDaoTests extends InvoiceDaoTestBase {
     private final int NUMBER_OF_DAY_BETWEEN_RETRIES = 8;
+    private final Clock clock = new DefaultClock();
 
     @Test
     public void testCreationAndRetrievalByAccount() {
@@ -50,7 +65,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
 
         invoiceDao.create(invoice);
 
-        List<Invoice> invoices = invoiceDao.getInvoicesByAccount(accountId.toString());
+        List<Invoice> invoices = invoiceDao.getInvoicesByAccount(accountId);
         assertNotNull(invoices);
         assertEquals(invoices.size(), 1);
         Invoice thisInvoice = invoices.get(0);
@@ -69,33 +84,33 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         UUID subscriptionId = UUID.randomUUID();
         DateTime startDate = new DateTime(2010, 1, 1, 0, 0, 0, 0);
         DateTime endDate = new DateTime(2010, 4, 1, 0, 0, 0, 0);
-        InvoiceItem invoiceItem = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate, endDate, "test", new BigDecimal("21.00"), new BigDecimal("7.00"), Currency.USD);
-        invoice.add(invoiceItem);
+        InvoiceItem invoiceItem = new DefaultInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, endDate, new BigDecimal("21.00"), new BigDecimal("7.00"), null, Currency.USD);
+        invoice.addInvoiceItem(invoiceItem);
         invoiceDao.create(invoice);
 
-        Invoice savedInvoice = invoiceDao.getById(invoiceId.toString());
+        Invoice savedInvoice = invoiceDao.getById(invoiceId);
         assertNotNull(savedInvoice);
         assertEquals(savedInvoice.getTotalAmount().compareTo(new BigDecimal("21.00")), 0);
-        assertEquals(savedInvoice.getAmountOutstanding().compareTo(new BigDecimal("21.00")), 0);
+        assertEquals(savedInvoice.getBalance().compareTo(new BigDecimal("21.00")), 0);
         assertEquals(savedInvoice.getAmountPaid(), BigDecimal.ZERO);
-        assertEquals(savedInvoice.getItems().size(), 1);
+        assertEquals(savedInvoice.getInvoiceItems().size(), 1);
 
         BigDecimal paymentAmount = new BigDecimal("11.00");
         UUID paymentAttemptId = UUID.randomUUID();
 
-        invoiceDao.notifyOfPaymentAttempt(new InvoicePayment(invoiceId, paymentAmount, Currency.USD, paymentAttemptId, new DateTime(DateTimeZone.UTC).plusDays(12)));
+        invoiceDao.notifyOfPaymentAttempt(new DefaultInvoicePayment(paymentAttemptId, invoiceId, clock.getUTCNow().plusDays(12), paymentAmount, Currency.USD));
 
-        Invoice retrievedInvoice = invoiceDao.getById(invoiceId.toString());
+        Invoice retrievedInvoice = invoiceDao.getById(invoiceId);
         assertNotNull(retrievedInvoice);
-        assertEquals(retrievedInvoice.getItems().size(), 1);
+        assertEquals(retrievedInvoice.getInvoiceItems().size(), 1);
         assertEquals(retrievedInvoice.getTotalAmount().compareTo(new BigDecimal("21.00")), 0);
-        assertEquals(retrievedInvoice.getAmountOutstanding().compareTo(new BigDecimal("10.00")), 0);
+        assertEquals(retrievedInvoice.getBalance().compareTo(new BigDecimal("10.00")), 0);
         assertEquals(retrievedInvoice.getAmountPaid().compareTo(new BigDecimal("11.00")), 0);
     }
 
     @Test
     public void testRetrievalForNonExistentInvoiceId() {
-        Invoice invoice = invoiceDao.getById(UUID.randomUUID().toString());
+        Invoice invoice = invoiceDao.getById(UUID.randomUUID());
         assertNull(invoice);
     }
 
@@ -110,12 +125,12 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         BigDecimal paymentAmount = new BigDecimal("14.0");
 
         invoiceDao.create(invoice);
+        invoiceDao.notifyOfPaymentAttempt(new DefaultInvoicePayment(paymentAttemptId, invoice.getId(), paymentAttemptDate, paymentAmount, Currency.USD));
 
-        invoiceDao.notifyOfPaymentAttempt(new InvoicePayment(invoice.getId(), paymentAmount, Currency.USD, paymentAttemptId, paymentAttemptDate));
-
-        invoice = invoiceDao.getById(invoice.getId().toString());
+        invoice = invoiceDao.getById(invoice.getId());
         assertEquals(invoice.getAmountPaid().compareTo(paymentAmount), 0);
-        assertTrue(invoice.getLastPaymentAttempt().equals(paymentAttemptDate));
+        assertEquals(invoice.getLastPaymentAttempt().compareTo(paymentAttemptDate), 0);
+        assertEquals(invoice.getNumberOfPayments(), 1);
     }
 
     @Test
@@ -127,10 +142,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         DateTime paymentAttemptDate = new DateTime(2011, 6, 24, 12, 14, 36, 0);
 
         invoiceDao.create(invoice);
-        invoiceDao.notifyOfPaymentAttempt(new InvoicePayment(invoice.getId(), UUID.randomUUID(), paymentAttemptDate));
+        invoiceDao.notifyOfPaymentAttempt(new DefaultInvoicePayment(invoice.getId(), paymentAttemptDate));
 
-        invoice = invoiceDao.getById(invoice.getId().toString());
-        assertTrue(invoice.getLastPaymentAttempt().equals(paymentAttemptDate));
+        invoice = invoiceDao.getById(invoice.getId());
+        assertEquals(invoice.getLastPaymentAttempt().compareTo(paymentAttemptDate), 0);
     }
 
     @Test
@@ -139,14 +154,14 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         DateTime targetDate = new DateTime(2011, 10, 6, 0, 0, 0, 0);
 
         // determine the number of existing invoices available for payment (to avoid side effects from other tests)
-        List<UUID> invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+        List<UUID> invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
         int existingInvoiceCount = invoices.size();
 
         UUID accountId = UUID.randomUUID();
         Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD);
 
         invoiceDao.create(invoice);
-        invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+        invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
         assertEquals(invoices.size(), existingInvoiceCount);
     }
 
@@ -166,73 +181,72 @@ 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, targetDate, endDate, "test", amount, rate, Currency.USD);
-        invoice.add(item);
+        DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", targetDate, endDate, amount, rate, null, Currency.USD);
+        invoice.addInvoiceItem(item);
         invoiceDao.create(invoice);
 
         // ensure that the number of invoices for payment has increased by 1
         int count;
-        invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+        invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
         List<Invoice> invoicesDue = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate);
         count = invoicesDue.size();
         assertEquals(invoices.size(), count);
 
         // attempt a payment; ensure that the number of invoices for payment has decreased by 1
         // (no retries for NUMBER_OF_DAYS_BETWEEN_RETRIES days)
-        invoiceDao.notifyOfPaymentAttempt(new InvoicePayment(invoice.getId(), UUID.randomUUID(), notionalDate));
-        invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+        invoiceDao.notifyOfPaymentAttempt(new DefaultInvoicePayment(invoice.getId(), notionalDate));
+        invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
         count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
         assertEquals(invoices.size(), count);
 
         // advance clock by NUMBER_OF_DAYS_BETWEEN_RETRIES days
         // ensure that number of invoices for payment has increased by 1 (retry)
         notionalDate = notionalDate.plusDays(NUMBER_OF_DAY_BETWEEN_RETRIES);
-        invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+        invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
         count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
         assertEquals(invoices.size(), count);
 
         // post successful partial payment; ensure that number of invoices for payment has decreased by 1
-        invoiceDao.notifyOfPaymentAttempt(new InvoicePayment(invoice.getId(), new BigDecimal("22.0000"), Currency.USD, UUID.randomUUID(), notionalDate));
+        invoiceDao.notifyOfPaymentAttempt(new DefaultInvoicePayment(UUID.randomUUID(), invoice.getId(), notionalDate, new BigDecimal("22.0000"), Currency.USD));
 
-        invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+        invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
         count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
         assertEquals(invoices.size(), count);
 
         // get invoice; verify amount paid is correct
-        invoice = invoiceDao.getById(invoiceId.toString());
+        invoice = invoiceDao.getById(invoiceId);
         assertEquals(invoice.getAmountPaid().compareTo(new BigDecimal("22.0")), 0);
 
         // advance clock NUMBER_OF_DAYS_BETWEEN_RETRIES days
         // ensure that number of invoices for payment has increased by 1 (retry)
         notionalDate = notionalDate.plusDays(NUMBER_OF_DAY_BETWEEN_RETRIES);
-        invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+        invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
         count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
         assertEquals(invoices.size(), count);
 
         // post completed payment; ensure that the number of invoices for payment has decreased by 1
-        invoiceDao.notifyOfPaymentAttempt(new InvoicePayment(invoice.getId(), new BigDecimal("5.0000"), Currency.USD, UUID.randomUUID(), notionalDate));
+        invoiceDao.notifyOfPaymentAttempt(new DefaultInvoicePayment(UUID.randomUUID(), invoice.getId(), notionalDate, new BigDecimal("5.0000"), Currency.USD));
 
-        invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+        invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
         count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
         assertEquals(invoices.size(), count);
 
         // get invoice; verify amount paid is correct
-        invoice = invoiceDao.getById(invoiceId.toString());
-        count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
+        invoice = invoiceDao.getById(invoiceId);
         assertEquals(invoice.getAmountPaid().compareTo(new BigDecimal("27.0")), 0);
 
         // advance clock by NUMBER_OF_DAYS_BETWEEN_RETRIES days
         // ensure that the number of invoices for payment hasn't changed
         notionalDate = notionalDate.plusDays(NUMBER_OF_DAY_BETWEEN_RETRIES);
-        invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+        invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
         count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
         assertEquals(invoices.size(), count);
     }
 
-    private List<Invoice> getInvoicesDueForPaymentAttempt(List<Invoice> invoices, DateTime date) {
+    private List<Invoice> getInvoicesDueForPaymentAttempt(final List<Invoice> invoices, final DateTime date) {
         List<Invoice> invoicesDue= new ArrayList<Invoice>();
 
-        for (Invoice invoice : invoices) {
+        for (final Invoice invoice : invoices) {
             if (invoice.isDueForPayment(date, NUMBER_OF_DAY_BETWEEN_RETRIES)) {
                 invoicesDue.add(invoice);
             }
@@ -262,16 +276,16 @@ 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, startDate, endDate, "test A", rate1, rate1, Currency.USD);
+        DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoiceId1, subscriptionId1, "test plan", "test A", startDate, endDate, rate1, rate1, null, Currency.USD);
         invoiceItemDao.create(item1);
 
-        DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoiceId1, subscriptionId2, startDate, endDate, "test B", rate2, rate2, Currency.USD);
+        DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoiceId1, subscriptionId2, "test plan", "test B", startDate, endDate, rate2, rate2, null, Currency.USD);
         invoiceItemDao.create(item2);
 
-        DefaultInvoiceItem item3 = new DefaultInvoiceItem(invoiceId1, subscriptionId3, startDate, endDate, "test C", rate3, rate3, Currency.USD);
+        DefaultInvoiceItem item3 = new DefaultInvoiceItem(invoiceId1, subscriptionId3, "test plan", "test C", startDate, endDate, rate3, rate3, null, Currency.USD);
         invoiceItemDao.create(item3);
 
-        DefaultInvoiceItem item4 = new DefaultInvoiceItem(invoiceId1, subscriptionId4, startDate, endDate, "test D", rate4, rate4, Currency.USD);
+        DefaultInvoiceItem item4 = new DefaultInvoiceItem(invoiceId1, subscriptionId4, "test plan", "test D", startDate, endDate, rate4, rate4, null, Currency.USD);
         invoiceItemDao.create(item4);
 
         // create invoice 2 (subscriptions 1-3)
@@ -283,29 +297,58 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         startDate = endDate;
         endDate = startDate.plusMonths(1);
 
-        DefaultInvoiceItem item5 = new DefaultInvoiceItem(invoiceId2, subscriptionId1, startDate, endDate, "test A", rate1, rate1, Currency.USD);
+        DefaultInvoiceItem item5 = new DefaultInvoiceItem(invoiceId2, subscriptionId1, "test plan", "test phase A", startDate, endDate, rate1, rate1, null, Currency.USD);
         invoiceItemDao.create(item5);
 
-        DefaultInvoiceItem item6 = new DefaultInvoiceItem(invoiceId2, subscriptionId2, startDate, endDate, "test B", rate2, rate2, Currency.USD);
+        DefaultInvoiceItem item6 = new DefaultInvoiceItem(invoiceId2, subscriptionId2, "test plan", "test phase B", startDate, endDate, rate2, rate2, null, Currency.USD);
         invoiceItemDao.create(item6);
 
-        DefaultInvoiceItem item7 = new DefaultInvoiceItem(invoiceId2, subscriptionId3, startDate, endDate, "test C", rate3, rate3, Currency.USD);
+        DefaultInvoiceItem item7 = new DefaultInvoiceItem(invoiceId2, subscriptionId3, "test plan", "test phase C", startDate, endDate, rate3, rate3, null, Currency.USD);
         invoiceItemDao.create(item7);
 
         // check that each subscription returns the correct number of invoices
-        List<Invoice> items1 = invoiceDao.getInvoicesBySubscription(subscriptionId1.toString());
+        List<Invoice> items1 = invoiceDao.getInvoicesBySubscription(subscriptionId1);
         assertEquals(items1.size(), 2);
 
-        List<Invoice> items2 = invoiceDao.getInvoicesBySubscription(subscriptionId2.toString());
+        List<Invoice> items2 = invoiceDao.getInvoicesBySubscription(subscriptionId2);
         assertEquals(items2.size(), 2);
 
-        List<Invoice> items3 = invoiceDao.getInvoicesBySubscription(subscriptionId3.toString());
+        List<Invoice> items3 = invoiceDao.getInvoicesBySubscription(subscriptionId3);
         assertEquals(items3.size(), 2);
 
-        List<Invoice> items4 = invoiceDao.getInvoicesBySubscription(subscriptionId4.toString());
+        List<Invoice> items4 = invoiceDao.getInvoicesBySubscription(subscriptionId4);
         assertEquals(items4.size(), 1);
     }
-    
+
+    @Test
+    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);
+        invoiceDao.create(invoice1);
+
+        DateTime targetDate2 = new DateTime(2011, 12, 6, 0, 0, 0, 0);
+        Invoice invoice2 = new DefaultInvoice(accountId, targetDate2, Currency.USD);
+        invoiceDao.create(invoice2);
+
+
+        List<Invoice> invoices;
+        invoices = invoiceDao.getInvoicesByAccount(accountId, new DateTime(2011, 1, 1, 0, 0, 0, 0));
+        assertEquals(invoices.size(), 2);
+
+        invoices = invoiceDao.getInvoicesByAccount(accountId, new DateTime(2011, 10, 6, 0, 0, 0, 0));
+        assertEquals(invoices.size(), 2);
+
+        invoices = invoiceDao.getInvoicesByAccount(accountId, new DateTime(2011, 10, 11, 0, 0, 0, 0));
+        assertEquals(invoices.size(), 1);
+
+        invoices = invoiceDao.getInvoicesByAccount(accountId, new DateTime(2011, 12, 6, 0, 0, 0, 0));
+        assertEquals(invoices.size(), 1);
+
+        invoices = invoiceDao.getInvoicesByAccount(accountId, new DateTime(2012, 1, 1, 0, 0, 0, 0));
+        assertEquals(invoices.size(), 0);
+    }
+
     @Test
     public void testAccountBalance() {
         UUID accountId = UUID.randomUUID();
@@ -319,23 +362,20 @@ 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(), startDate, endDate, "test A", rate1, rate1, Currency.USD);
+        DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase A", startDate, endDate, rate1, rate1, null, Currency.USD);
         invoiceItemDao.create(item1);
 
-        DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), startDate, endDate, "test B", rate2, rate2, Currency.USD);
+        DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase B", startDate, endDate, rate2, rate2, null, Currency.USD);
         invoiceItemDao.create(item2);
 
         BigDecimal payment1 = new BigDecimal("48.0");
-        
-        // TODO - reenable when DefaultInvoicePayement is visible in this branch
-        //InvoicePayment payment = new DefaultInvoicePayment(invoice1.getId(), new DateTime(), payment1, Currency.USD);
-        //invoicePaymentDao.create(payment);
-        //
-        //BigDecimal balance = invoiceDao.getAccountBalance(accountId);
-        //assertEquals(balance.compareTo(rate1.add(rate2).subtract(payment1)), 0);
+        InvoicePayment payment = new DefaultInvoicePayment(invoice1.getId(), new DateTime(), payment1, Currency.USD);
+        invoicePaymentDao.create(payment);
 
+        BigDecimal balance = invoiceDao.getAccountBalance(accountId);
+        assertEquals(balance.compareTo(rate1.add(rate2).subtract(payment1)), 0);
     }
-    
+
     @Test
     public void testAccountBalanceWithNoPayments() {
         UUID accountId = UUID.randomUUID();
@@ -349,17 +389,16 @@ 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(), startDate, endDate, "test A", rate1, rate1, Currency.USD);
+        DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase A", startDate, endDate, rate1, rate1, null, Currency.USD);
         invoiceItemDao.create(item1);
 
-        DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), startDate, endDate, "test B", rate2, rate2, Currency.USD);
+        DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase B", startDate, endDate, rate2, rate2, null, Currency.USD);
         invoiceItemDao.create(item2);
 
         BigDecimal balance = invoiceDao.getAccountBalance(accountId);
         assertEquals(balance.compareTo(rate1.add(rate2)), 0);
     }
 
-
     @Test
     public void testAccountBalanceWithNoInvoiceItems() {
         UUID accountId = UUID.randomUUID();
@@ -367,12 +406,153 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         Invoice invoice1 = new DefaultInvoice(accountId, targetDate1, Currency.USD);
         invoiceDao.create(invoice1);
 
-        // TODO - reenable when DefaultInvoicePayement is visible in this branch
-        //BigDecimal payment1 = new BigDecimal("48.0");
-        //InvoicePayment payment = new DefaultInvoicePayment(invoice1.getId(), new DateTime(), payment1, Currency.USD);
-        //invoicePaymentDao.create(payment);
-        //
-        //BigDecimal balance = invoiceDao.getAccountBalance(accountId);
-        //assertEquals(balance.compareTo(BigDecimal.ZERO.subtract(payment1)), 0);
+        BigDecimal payment1 = new BigDecimal("48.0");
+        InvoicePayment payment = new DefaultInvoicePayment(invoice1.getId(), new DateTime(), payment1, Currency.USD);
+        invoicePaymentDao.create(payment);
+
+        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);
+        invoiceDao.create(invoice1);
+
+        DateTime startDate = new DateTime(2011, 3, 1, 0, 0, 0, 0);
+        DateTime endDate = startDate.plusMonths(1);
+
+        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);
+
+        DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase B", startDate, endDate, rate2, rate2, null, Currency.USD);
+        invoiceItemDao.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);
+        invoiceDao.create(invoice2);
+
+        DateTime startDate2 = new DateTime(2011, 6, 1, 0, 0, 0, 0);
+        DateTime endDate2 = startDate2.plusMonths(3);
+
+        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);
+
+        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(), 2);
+    }
+
+    /*
+     *
+     * this test verifies that immediate changes give the correct results
+     *
+     */
+    @Test
+    public void testInvoiceGenerationForImmediateChanges() {
+        InvoiceGenerator generator = new DefaultInvoiceGenerator();
+
+        UUID accountId = UUID.randomUUID();
+        InvoiceItemList invoiceItemList = new InvoiceItemList();
+        DateTime targetDate = new DateTime(2011, 2, 16, 0, 0, 0, 0);
+
+        // generate first invoice
+        DefaultPrice price1 = new DefaultPrice(TEN, Currency.USD);
+        MockInternationalPrice recurringPrice = new MockInternationalPrice(price1);
+        MockPlanPhase phase1 = new MockPlanPhase(recurringPrice, null, BillingPeriod.MONTHLY, PhaseType.TRIAL);
+        MockPlan plan1 = new MockPlan(phase1);
+
+        Subscription subscription = new MockSubscription();
+        DateTime effectiveDate1 = new DateTime(2011, 2, 1, 0, 0, 0, 0);
+        BillingEvent event1 = new DefaultBillingEvent(subscription, effectiveDate1, plan1, phase1, null,
+                                                      recurringPrice, BillingPeriod.MONTHLY, 1, BillingModeType.IN_ADVANCE,
+                                                      "testEvent1", SubscriptionTransitionType.CREATE);
+
+        BillingEventSet events = new BillingEventSet();
+        events.add(event1);
+
+        Invoice invoice1 = generator.generateInvoice(accountId, events, invoiceItemList, targetDate, Currency.USD);
+        assertEquals(invoice1.getBalance(), TEN);
+        invoiceItemList.addAll(invoice1.getInvoiceItems());
+
+        // generate second invoice
+        DefaultPrice price2 = new DefaultPrice(TWENTY, Currency.USD);
+        MockInternationalPrice recurringPrice2 = new MockInternationalPrice(price2);
+        MockPlanPhase phase2 = new MockPlanPhase(recurringPrice, null, BillingPeriod.MONTHLY, PhaseType.TRIAL);
+        MockPlan plan2 = new MockPlan(phase2);
+
+        DateTime effectiveDate2 = new DateTime(2011, 2, 15, 0, 0, 0, 0);
+        BillingEvent event2 = new DefaultBillingEvent(subscription, effectiveDate2, plan2, phase2, null,
+                                                      recurringPrice2, BillingPeriod.MONTHLY, 1, BillingModeType.IN_ADVANCE,
+                                                      "testEvent2", SubscriptionTransitionType.CREATE);
+        events.add(event2);
+
+        // second invoice should be for one half (14/28 days) the difference between the rate plans
+        // this is a temporary state, since it actually contains an adjusting item that properly belong to invoice 1
+        Invoice invoice2 = generator.generateInvoice(accountId, events, invoiceItemList, targetDate, Currency.USD);
+        assertEquals(invoice2.getBalance(), FIVE);
+        invoiceItemList.addAll(invoice2.getInvoiceItems());
+
+        invoiceDao.create(invoice1);
+        invoiceDao.create(invoice2);
+
+        Invoice savedInvoice1 = invoiceDao.getById(invoice1.getId());
+        assertEquals(savedInvoice1.getTotalAmount(), ZERO);
+
+        Invoice savedInvoice2 = invoiceDao.getById(invoice2.getId());
+        assertEquals(savedInvoice2.getTotalAmount(), FIFTEEN);
+    }
+
+    @Test
+    public void testInvoiceForFreeTrial() {
+        DefaultPrice price = new DefaultPrice(BigDecimal.ZERO, Currency.USD);
+        MockInternationalPrice recurringPrice = new MockInternationalPrice(price);
+        MockPlanPhase phase = new MockPlanPhase(recurringPrice, null);
+        MockPlan plan = new MockPlan(phase);
+
+        Subscription subscription = new MockSubscription();
+        DateTime effectiveDate = buildDateTime(2011, 1, 1);
+
+        BillingEvent event = new DefaultBillingEvent(subscription, effectiveDate, plan, phase, null,
+                                                     recurringPrice, BillingPeriod.MONTHLY, 15, BillingModeType.IN_ADVANCE,
+                                                     "testEvent", SubscriptionTransitionType.CREATE);
+        BillingEventSet events = new BillingEventSet();
+        events.add(event);
+
+        DateTime targetDate = buildDateTime(2011, 1, 15);
+        InvoiceGenerator generator = new DefaultInvoiceGenerator();
+        Invoice invoice = generator.generateInvoice(UUID.randomUUID(), events, null, targetDate, Currency.USD);
+        assertEquals(invoice.getNumberOfItems(), 1);
+        assertEquals(invoice.getTotalAmount().compareTo(ZERO), 0);
+    }
+
+    @Test
+    public void testInvoiceForEmptyEventSet() {
+        InvoiceGenerator generator = new DefaultInvoiceGenerator();
+        BillingEventSet events = new BillingEventSet();
+        Invoice invoice = generator.generateInvoice(UUID.randomUUID(), events, null, new DateTime(), Currency.USD);
+        assertNull(invoice);
     }
 }
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 dc2afd8..b2c2f23 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
@@ -45,7 +45,7 @@ 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, startDate, endDate, "test", rate, rate, Currency.USD);
+        InvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, endDate, rate, rate, rate, Currency.USD);
         invoiceItemDao.create(item);
 
         InvoiceItem thisItem = invoiceItemDao.getById(item.getId().toString());
@@ -55,9 +55,9 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
         assertEquals(thisItem.getSubscriptionId(), item.getSubscriptionId());
         assertEquals(thisItem.getStartDate(), item.getStartDate());
         assertEquals(thisItem.getEndDate(), item.getEndDate());
-        assertEquals(thisItem.getDescription(), item.getDescription());
-        assertEquals(thisItem.getAmount().compareTo(item.getAmount()), 0);
-        assertEquals(thisItem.getRate().compareTo(item.getRate()), 0);
+        assertEquals(thisItem.getRecurringAmount().compareTo(item.getRecurringAmount()), 0);
+        assertEquals(thisItem.getRecurringRate().compareTo(item.getRecurringRate()), 0);
+        assertEquals(thisItem.getFixedAmount().compareTo(item.getFixedAmount()), 0);
         assertEquals(thisItem.getCurrency(), item.getCurrency());
     }
 
@@ -69,7 +69,7 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
 
         for (int i = 0; i < 3; i++) {
             UUID invoiceId = UUID.randomUUID();
-            DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate.plusMonths(i), startDate.plusMonths(i + 1), "test", rate, rate, Currency.USD);
+            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);
         }
 
@@ -86,7 +86,7 @@ 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, startDate, startDate.plusMonths(1), "test", amount, amount, Currency.USD);
+            DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, startDate.plusMonths(1), amount, amount, amount, Currency.USD);
             invoiceItemDao.create(item);
         }
 
@@ -107,7 +107,7 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
         BigDecimal rate = new BigDecimal("20.00");
 
         UUID subscriptionId = UUID.randomUUID();
-        DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate, startDate.plusMonths(1), "test", rate, rate, Currency.USD);
+        DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, startDate.plusMonths(1), rate, rate, rate, Currency.USD);
         invoiceItemDao.create(item);
 
         List<InvoiceItem> items = invoiceItemDao.getInvoiceItemsByAccount(accountId.toString());
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
index 2ef150c..cf6c112 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
@@ -18,150 +18,133 @@ package com.ning.billing.invoice.dao;
 
 import java.math.BigDecimal;
 import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Date;
 import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.UUID;
 
+import com.ning.billing.invoice.api.InvoicePayment;
+import com.ning.billing.util.bus.Bus;
 import org.joda.time.DateTime;
 
 import com.google.inject.Inject;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.invoice.api.user.DefaultInvoiceCreationNotification;
-import com.ning.billing.invoice.model.DefaultInvoice;
-import com.ning.billing.payment.api.InvoicePayment;
-import com.ning.billing.util.eventbus.EventBus;
-import com.ning.billing.util.eventbus.EventBus.EventBusException;
 
 public class MockInvoiceDao implements InvoiceDao {
-    private final EventBus eventBus;
+    private final Bus eventBus;
     private final Object monitor = new Object();
-    private final Map<String, Invoice> invoices = new LinkedHashMap<String, Invoice>();
-    private final Map<UUID, InvoicePayment> invoicePayments = new LinkedHashMap<UUID, InvoicePayment>();
+    private final Map<UUID, Invoice> invoices = new LinkedHashMap<UUID, Invoice>();
 
     @Inject
-    public MockInvoiceDao(EventBus eventBus) {
+    public MockInvoiceDao(Bus eventBus) {
         this.eventBus = eventBus;
     }
 
     @Override
     public void create(Invoice invoice) {
         synchronized (monitor) {
-            invoices.put(invoice.getId().toString(), invoice);
+            invoices.put(invoice.getId(), invoice);
         }
         try {
             eventBus.post(new DefaultInvoiceCreationNotification(invoice.getId(), invoice.getAccountId(),
-                                                                 invoice.getAmountOutstanding(), invoice.getCurrency(),
+                                                                 invoice.getBalance(), invoice.getCurrency(),
                                                                  invoice.getInvoiceDate()));
         }
-        catch (EventBusException ex) {
+        catch (Bus.EventBusException ex) {
             throw new RuntimeException(ex);
         }
     }
 
-    private Invoice munge(Invoice invoice) {
-        if (invoice == null) {
-            return null;
-        }
-
-        DateTime lastPaymentDate = null;
-        BigDecimal amountPaid = new BigDecimal("0");
-
-        for (InvoicePayment invoicePayment : invoicePayments.values()) {
-            if (invoicePayment.getInvoiceId().equals(invoice.getId().toString())) {
-                if (lastPaymentDate == null || lastPaymentDate.isBefore(invoicePayment.getPaymentAttemptDate())) {
-                    lastPaymentDate = invoicePayment.getPaymentAttemptDate();
-                }
-                if (invoicePayment.getAmount() != null) {
-                    amountPaid.add(invoicePayment.getAmount());
-                }
-            }
+    @Override
+    public Invoice getById(UUID id) {
+        synchronized (monitor) {
+            return invoices.get(id);
         }
-        return new DefaultInvoice(invoice.getId(),
-                                  invoice.getAccountId(),
-                                  invoice.getInvoiceDate(),
-                                  invoice.getTargetDate(),
-                                  invoice.getCurrency(),
-                                  lastPaymentDate,
-                                  amountPaid,
-                                  invoice.getItems());
     }
 
-    private List<Invoice> munge(Collection<Invoice> invoices) {
-        List<Invoice> result = new ArrayList<Invoice>();
-        for (Invoice invoice : invoices) {
-            result.add(munge(invoice));
+    @Override
+    public List<Invoice> get() {
+        synchronized (monitor) {
+            return new ArrayList<Invoice>(invoices.values());
         }
-        return result;
     }
 
     @Override
-    public Invoice getById(String id) {
+    public List<Invoice> getInvoicesByAccount(UUID accountId) {
+        List<Invoice> result = new ArrayList<Invoice>();
+
         synchronized (monitor) {
-            return munge(invoices.get(id));
+            for (Invoice invoice : invoices.values()) {
+                if (accountId.equals(invoice.getAccountId())) {
+                    result.add(invoice);
+                }
+            }
         }
+        return result;
     }
 
     @Override
-    public List<Invoice> get() {
+    public List<Invoice> getInvoicesByAccount(UUID accountId, DateTime fromDate) {
+        List<Invoice> invoicesForAccount = new ArrayList<Invoice>();
+
         synchronized (monitor) {
-            return munge(invoices.values());
+            for (Invoice invoice : get()) {
+                if (accountId.equals(invoice.getAccountId()) && !invoice.getTargetDate().isBefore(fromDate)) {
+                    invoicesForAccount.add(invoice);
+                }
+            }
         }
+
+        return invoicesForAccount;
     }
 
     @Override
-    public List<Invoice> getInvoicesByAccount(String accountId) {
-        List<Invoice> result = new ArrayList<Invoice>();
+    public List<InvoiceItem> getInvoiceItemsByAccount(UUID accountId) {
+        List<InvoiceItem> invoiceItemsForAccount = new ArrayList<InvoiceItem>();
 
         synchronized (monitor) {
-            for (Invoice invoice : invoices.values()) {
-                if (accountId.equals(invoice.getAccountId().toString())) {
-                    result.add(invoice);
+            for (Invoice invoice : get()) {
+                if (accountId.equals(invoice.getAccountId())) {
+                    invoiceItemsForAccount.addAll(invoice.getInvoiceItems());
                 }
             }
         }
-        return munge(result);
+
+        return invoiceItemsForAccount;
     }
 
     @Override
-    public List<Invoice> getInvoicesBySubscription(String subscriptionId) {
+    public List<Invoice> getInvoicesBySubscription(UUID subscriptionId) {
         List<Invoice> result = new ArrayList<Invoice>();
 
         synchronized (monitor) {
             for (Invoice invoice : invoices.values()) {
-                for (InvoiceItem item : invoice.getItems()) {
-                    if (subscriptionId.equals(item.getSubscriptionId().toString())) {
+                for (InvoiceItem item : invoice.getInvoiceItems()) {
+                    if (subscriptionId.equals(item.getSubscriptionId())) {
                         result.add(invoice);
                         break;
                     }
                 }
             }
         }
-        return munge(result);
+        return result;
     }
 
     @Override
-    public List<UUID> getInvoicesForPayment(Date targetDate, int numberOfDays) {
-        Set<UUID> result = new LinkedHashSet<UUID>();
+    public List<UUID> getInvoicesForPayment(DateTime targetDate, int numberOfDays) {
+        List<UUID> result = new ArrayList<UUID>();
 
         synchronized (monitor) {
-            for (InvoicePayment invoicePayment : invoicePayments.values()) {
-                Invoice invoice = invoices.get(invoicePayment.getInvoiceId());
-                if ((invoice != null) &&
-                    (((invoicePayment.getPaymentAttemptDate() == null) || !invoicePayment.getPaymentAttemptDate().plusDays(numberOfDays).isAfter(targetDate.getTime())) &&
-                    (invoice.getTotalAmount() != null) && invoice.getTotalAmount().doubleValue() >= 0) &&
-                    ((invoicePayment.getAmount() == null) || invoicePayment.getAmount().doubleValue() >= invoice.getTotalAmount().doubleValue())) {
-                        result.add(invoice.getId());
+            for (Invoice invoice : invoices.values()) {
+                if (invoice.isDueForPayment(targetDate, numberOfDays)) {
+                    result.add(invoice.getId());
                 }
             }
         }
 
-        return new ArrayList<UUID>(result);
+        return result;
     }
 
     @Override
@@ -169,11 +152,13 @@ public class MockInvoiceDao implements InvoiceDao {
     }
 
     @Override
-    public String getInvoiceIdByPaymentAttemptId(UUID paymentAttemptId) {
+    public UUID getInvoiceIdByPaymentAttemptId(UUID paymentAttemptId) {
         synchronized(monitor) {
-            for (InvoicePayment invoicePayment : invoicePayments.values()) {
-                if (paymentAttemptId.toString().equals(invoicePayment.getPaymentAttemptId())) {
-                    return invoicePayment.getInvoiceId().toString();
+            for (Invoice invoice : invoices.values()) {
+                for (InvoicePayment payment : invoice.getPayments()) {
+                    if (paymentAttemptId.equals(payment.getPaymentAttemptId())) {
+                        return invoice.getId();
+                    }
                 }
             }
         }
@@ -183,24 +168,51 @@ public class MockInvoiceDao implements InvoiceDao {
     @Override
     public InvoicePayment getInvoicePayment(UUID paymentAttemptId) {
         synchronized(monitor) {
-            return invoicePayments.get(paymentAttemptId);
+            for (Invoice invoice : invoices.values()) {
+                for (InvoicePayment payment : invoice.getPayments()) {
+                    if (paymentAttemptId.equals(payment.getPaymentAttemptId())) {
+                        return payment;
+                    }
+                }
+            }
         }
+
+        return null;
     }
 
     @Override
     public void notifyOfPaymentAttempt(InvoicePayment invoicePayment) {
-      synchronized (monitor) {
-          invoicePayments.put(invoicePayment.getPaymentAttemptId(), invoicePayment);
-      }
+        synchronized (monitor) {
+            Invoice invoice = invoices.get(invoicePayment.getInvoiceId());
+            if (invoice != null) {
+                invoice.addPayment(invoicePayment);
+            }
+        }
+    }
+
+    @Override
+    public BigDecimal getAccountBalance(UUID accountId) {
+        BigDecimal balance = BigDecimal.ZERO;
+
+        for (Invoice invoice : get()) {
+            if (accountId.equals(invoice.getAccountId())) {
+                balance = balance.add(invoice.getBalance());
+            }
+        }
+
+        return balance;
     }
 
-	@Override
-	public BigDecimal getAccountBalance(UUID accountId) {
-		List<Invoice> invoices = getInvoicesByAccount(accountId.toString());
-		BigDecimal result = BigDecimal.ZERO;
-		for(Invoice invoice : invoices) {
-			result = result.add(invoice.getAmountOutstanding());
-		}
-		return result;
-	}
+    @Override
+    public List<Invoice> getUnpaidInvoicesByAccountId(UUID accountId, DateTime upToDate) {
+        List<Invoice> unpaidInvoices = new ArrayList<Invoice>();
+
+        for (Invoice invoice : get()) {
+            if (accountId.equals(invoice.getAccountId()) && (invoice.getBalance().compareTo(BigDecimal.ZERO) > 0)) {
+                unpaidInvoices.add(invoice);
+            }
+        }
+
+        return unpaidInvoices;
+    }
 }
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
new file mode 100644
index 0000000..005a605
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/MockSubscription.java
@@ -0,0 +1,122 @@
+/*
+ * 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.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;
+import org.joda.time.DateTime;
+
+import java.util.List;
+import java.util.UUID;
+
+public class MockSubscription implements Subscription {
+    private UUID subscriptionId = UUID.randomUUID();
+
+    @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() {
+        return subscriptionId;
+    }
+
+    @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();
+    }
+}
\ 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 3705289..b252531 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
@@ -16,19 +16,33 @@
 
 package com.ning.billing.invoice.glue;
 
+import java.io.IOException;
+
+import com.ning.billing.invoice.api.test.InvoiceTestApi;
+import com.ning.billing.invoice.api.test.DefaultInvoiceTestApi;
+import com.ning.billing.invoice.dao.InvoicePaymentSqlDao;
+import com.ning.billing.util.glue.GlobalLockerModule;
+import 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.glue.EventBusModule;
-import org.skife.jdbi.v2.IDBI;
-
-import java.io.IOException;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.DefaultClock;
+import com.ning.billing.util.glue.BusModule;
+import com.ning.billing.util.notificationq.MockNotificationQueueService;
+import com.ning.billing.util.notificationq.NotificationQueueService;
 
 public class InvoiceModuleWithEmbeddedDb extends InvoiceModule {
     private final MysqlTestingHelper helper = new MysqlTestingHelper();
     private IDBI dbi;
 
-    public void createDb(String ddl) throws IOException {
+    public void startDb() throws IOException {
         helper.startMysql();
+    }
+
+    public void initDb(final String ddl) throws IOException {
         helper.initDb(ddl);
     }
 
@@ -36,15 +50,34 @@ public class InvoiceModuleWithEmbeddedDb extends InvoiceModule {
         helper.stopMysql();
     }
 
-    public InvoiceItemSqlDao getInvoiceItemDao() {
+    public InvoiceItemSqlDao getInvoiceItemSqlDao() {
         return dbi.onDemand(InvoiceItemSqlDao.class);
     }
 
+    public InvoicePaymentSqlDao getInvoicePaymentSqlDao() {
+        return dbi.onDemand(InvoicePaymentSqlDao.class);
+    }
+
+    private void installNotificationQueue() {
+        bind(NotificationQueueService.class).to(MockNotificationQueueService.class).asEagerSingleton();
+    }
+
     @Override
     public void configure() {
         dbi = helper.getDBI();
         bind(IDBI.class).toInstance(dbi);
+
+        bind(Clock.class).to(DefaultClock.class).asEagerSingleton();
+        installNotificationQueue();
+        install(new AccountModule());
+        install(new CatalogModule());
+        install(new EntitlementModule());
+        install(new GlobalLockerModule());
+
         super.configure();
-        install(new EventBusModule());
+
+        bind(InvoiceTestApi.class).to(DefaultInvoiceTestApi.class).asEagerSingleton();
+
+        install(new BusModule());
     }
 }
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 d01301b..e5e699b 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,11 +18,34 @@ 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;
 
 public class InvoiceModuleWithMocks extends InvoiceModule {
     @Override
     protected void installInvoiceDao() {
         bind(MockInvoiceDao.class).asEagerSingleton();
         bind(InvoiceDao.class).to(MockInvoiceDao.class);
+        bind(GlobalLocker.class).to(MockGlobalLocker.class).asEagerSingleton();
+    }
+
+    @Override
+    protected void installGlobalLocker() {
+        bind(GlobalLocker.class).to(MockGlobalLocker.class).asEagerSingleton();
+    }
+
+    @Override
+    protected void installInvoiceListener() {
+
+    }
+
+    @Override
+    protected void installNotifier() {
+
+    }
+
+    @Override
+    protected void installInvoiceService() {
+
     }
 }
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
new file mode 100644
index 0000000..d2fcdee
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java
@@ -0,0 +1,164 @@
+/*
+ * 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.io.IOException;
+import java.sql.SQLException;
+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;
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.Transaction;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.slf4j.Logger;
+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.config.InvoiceConfig;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.lifecycle.KillbillService.ServiceException;
+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;
+	private DefaultNextBillingDateNotifier notifier;
+	private DummySqlTest dao;
+	private Bus eventBus;
+	private MysqlTestingHelper helper;
+
+	@BeforeClass(groups={"setup"})
+	public void setup() throws ServiceException, IOException, ClassNotFoundException, SQLException {
+		//TestApiBase.loadSystemPropertiesFromClasspath("/entitlement.properties");
+        final Injector g = Guice.createInjector(Stage.PRODUCTION,  new AbstractModule() {
+			protected void configure() {
+				 bind(Clock.class).to(ClockMock.class).asEagerSingleton();
+				 bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
+				 bind(NotificationQueueService.class).to(DefaultNotificationQueueService.class).asEagerSingleton();
+				 final InvoiceConfig invoiceConfig = new ConfigurationObjectFactory(System.getProperties()).build(InvoiceConfig.class);
+				 bind(InvoiceConfig.class).toInstance(invoiceConfig);
+				 final CatalogConfig catalogConfig = new ConfigurationObjectFactory(System.getProperties()).build(CatalogConfig.class);
+                 bind(CatalogConfig.class).toInstance(catalogConfig);
+                 bind(CatalogService.class).to(DefaultCatalogService.class).asEagerSingleton();
+                 final MysqlTestingHelper helper = new MysqlTestingHelper();
+				 bind(MysqlTestingHelper.class).toInstance(helper);
+				 IDBI dbi = helper.getDBI();
+				 bind(IDBI.class).toInstance(dbi);
+                 bind(EntitlementDao.class).to(EntitlementSqlDao.class).asEagerSingleton();
+			}
+        });
+
+        clock = g.getInstance(Clock.class);
+        IDBI dbi = g.getInstance(IDBI.class);
+        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));
+        startMysql();
+	}
+
+	private void startMysql() throws IOException, ClassNotFoundException, SQLException {
+		final String ddl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
+		final String testDdl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl_test.sql"));
+		final String entitlementDdl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
+		helper.startMysql();
+		helper.initDb(ddl);
+		helper.initDb(testDdl);
+        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")
+	public void test() throws Exception {
+		final UUID subscriptionId = new UUID(0L,1000L);
+		final DateTime now = new DateTime();
+		final DateTime readyTime = now.plusMillis(2000);
+
+		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);
+				return null;
+			}
+		});
+
+		// Move time in the future after the notification effectiveDate
+		((ClockMock) clock).setDeltaFromReality(3000);
+
+
+	    await().atMost(1, MINUTES).until(new Callable<Boolean>() {
+	            @Override
+	            public Boolean call() throws Exception {
+	                return listener.getEventCount() == 1;
+	            }
+	        });
+
+		Assert.assertEquals(listener.getEventCount(), 1);
+		Assert.assertEquals(listener.getLatestEvent().getSubscriptionId(), 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 bf88c72..11f8d62 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
@@ -16,18 +16,14 @@
 
 package com.ning.billing.invoice.tests;
 
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-
-import java.math.BigDecimal;
-import java.util.UUID;
-
-import org.joda.time.DateTime;
-import org.testng.annotations.Test;
-
-import com.ning.billing.catalog.MockCatalog;
+import com.ning.billing.catalog.DefaultPrice;
+import com.ning.billing.catalog.MockInternationalPrice;
+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;
 import com.ning.billing.entitlement.api.billing.BillingEvent;
@@ -36,26 +32,37 @@ import com.ning.billing.entitlement.api.billing.DefaultBillingEvent;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
 import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.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.InvoiceGenerator;
 import com.ning.billing.invoice.model.InvoiceItemList;
+import org.joda.time.DateTime;
+import org.testng.annotations.Test;
+
+import javax.annotation.Nullable;
+import java.math.BigDecimal;
+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 = {"invoicing", "invoiceGenerator"})
+@Test(groups = {"fast", "invoicing", "invoiceGenerator"})
 public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
     private final InvoiceGenerator generator = new DefaultInvoiceGenerator();
 
     @Test
     public void testWithNullEventSetAndNullInvoiceSet() {
         UUID accountId = UUID.randomUUID();
-        Invoice invoice = generator.generateInvoice(accountId, null, null, new DateTime(), Currency.USD);
+        Invoice invoice = generator.generateInvoice(accountId, new BillingEventSet(), new InvoiceItemList(), new DateTime(), Currency.USD);
 
-        assertNotNull(invoice);
-        assertEquals(invoice.getNumberOfItems(), 0);
-        assertEquals(invoice.getTotalAmount(), ZERO);
+        assertNull(invoice);
     }
 
     @Test
@@ -66,9 +73,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         UUID accountId = UUID.randomUUID();
         Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, new DateTime(), Currency.USD);
 
-        assertNotNull(invoice);
-        assertEquals(invoice.getNumberOfItems(), 0);
-        assertEquals(invoice.getTotalAmount(), ZERO);
+        assertNull(invoice);
     }
 
     @Test
@@ -77,13 +82,12 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
 
         Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
         DateTime startDate = buildDateTime(2011, 9, 1);
-        MockCatalog catalog = new MockCatalog();
-        Plan plan = catalog.getCurrentPlans()[0];
-        PlanPhase phase = plan.getAllPhases()[0];
+
+        Plan plan = new MockPlan();
+        BigDecimal rate1 = TEN;
+        PlanPhase phase = createMockMonthlyPlanPhase(rate1);
         
-        BillingEvent event = new DefaultBillingEvent(sub, startDate, plan, phase,
-                new InternationalPriceMock(ZERO),new InternationalPriceMock(TEN), BillingPeriod.MONTHLY,
-                                               1, BillingModeType.IN_ADVANCE, "Test");
+        BillingEvent event = createBillingEvent(sub.getId(), startDate, plan, phase, 1);
         events.add(event);
 
         InvoiceItemList existingInvoiceItems = new InvoiceItemList();
@@ -95,6 +99,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         assertNotNull(invoice);
         assertEquals(invoice.getNumberOfItems(), 1);
         assertEquals(invoice.getTotalAmount(), TWENTY);
+        assertEquals(invoice.getInvoiceItems().get(0).getSubscriptionId(), sub.getId());
     }
 
     @Test
@@ -103,13 +108,11 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
 
         Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
         DateTime startDate = buildDateTime(2011, 9, 1);
-        MockCatalog catalog = new MockCatalog();
-        Plan plan = catalog.getCurrentPlans()[0];
-        PlanPhase phase = plan.getAllPhases()[0];
+
+        Plan plan = new MockPlan();
         BigDecimal rate = TEN;
-        BillingEvent event = new DefaultBillingEvent(sub, startDate, plan, phase,
-                new InternationalPriceMock(ZERO), new InternationalPriceMock(rate), BillingPeriod.MONTHLY,
-                                               15, BillingModeType.IN_ADVANCE,"Test");
+        PlanPhase phase = createMockMonthlyPlanPhase(rate);
+        BillingEvent event = createBillingEvent(sub.getId(), startDate, plan, phase, 15);
         events.add(event);
 
         InvoiceItemList existingInvoiceItems = new InvoiceItemList();
@@ -131,24 +134,20 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
     public void testTwoMonthlySubscriptionsWithAlignedBillingDates() {
         BillingEventSet events = new BillingEventSet();
 
-        MockCatalog catalog = new MockCatalog();
-        Plan plan1 = catalog.getCurrentPlans()[0];
-        PlanPhase phase1 = plan1.getAllPhases()[0];
-        Plan plan2 = catalog.getCurrentPlans()[1];
-        PlanPhase phase2 = plan2.getAllPhases()[0];
+        Plan plan1 = new MockPlan();
+        BigDecimal rate1 = FIVE;
+        PlanPhase phase1 = createMockMonthlyPlanPhase(rate1);
+
+        Plan plan2 = new MockPlan();
+        BigDecimal rate2 = TEN;
+        PlanPhase phase2 = createMockMonthlyPlanPhase(rate2);
         
         Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
 
-        BillingEvent event1 = new DefaultBillingEvent(sub, buildDateTime(2011, 9, 1),
-                                               plan1,phase1,
-                                               new InternationalPriceMock(ZERO), new InternationalPriceMock(FIVE), BillingPeriod.MONTHLY,
-                                               1, BillingModeType.IN_ADVANCE, "Test");
+        BillingEvent event1 = createBillingEvent(sub.getId(), buildDateTime(2011, 9, 1), plan1, phase1, 1);
         events.add(event1);
 
-        BillingEvent event2 = new DefaultBillingEvent(sub, buildDateTime(2011, 10, 1),
-                                               plan2,phase2,
-                                               new InternationalPriceMock(ZERO), new InternationalPriceMock(TEN), BillingPeriod.MONTHLY,
-                                               1, BillingModeType.IN_ADVANCE,"Test");
+        BillingEvent event2 = createBillingEvent(sub.getId(), buildDateTime(2011, 10, 1), plan2, phase2, 1);
         events.add(event2);
 
         InvoiceItemList existingInvoiceItems = new InvoiceItemList();
@@ -158,29 +157,25 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
 
         assertNotNull(invoice);
         assertEquals(invoice.getNumberOfItems(), 2);
-        assertEquals(invoice.getTotalAmount(), FIVE.multiply(TWO).add(TEN).setScale(NUMBER_OF_DECIMALS));
+        assertEquals(invoice.getTotalAmount(), rate1.add(rate2).setScale(NUMBER_OF_DECIMALS));
     }
 
     @Test
     public void testOnePlan_TwoMonthlyPhases_ChangeImmediate() {
         BillingEventSet events = new BillingEventSet();
 
-        MockCatalog catalog = new MockCatalog();
-        Plan plan1 = catalog.getCurrentPlans()[0];
-        PlanPhase phase1 = plan1.getAllPhases()[0];
+        Plan plan1 = new MockPlan();
+        BigDecimal rate1 = FIVE;
+        PlanPhase phase1 = createMockMonthlyPlanPhase(rate1);
 
         
         Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
-        BillingEvent event1 = new DefaultBillingEvent(sub, buildDateTime(2011, 9, 1),
-                                               plan1,phase1,
-                                               new InternationalPriceMock(ZERO),new InternationalPriceMock(FIVE), BillingPeriod.MONTHLY,
-                                               1, BillingModeType.IN_ADVANCE,"Test");
+        BillingEvent event1 = createBillingEvent(sub.getId(), buildDateTime(2011, 9, 1), plan1,phase1, 1);
         events.add(event1);
 
-        BillingEvent event2 = new DefaultBillingEvent(sub, buildDateTime(2011, 10, 15),
-                                               plan1,phase1, //technically should be a different phase but it doesn't impact the test
-                                               new InternationalPriceMock(ZERO),new InternationalPriceMock(TEN), BillingPeriod.MONTHLY,
-                                               15, BillingModeType.IN_ADVANCE,"Test");
+        BigDecimal rate2 = TEN;
+        PlanPhase phase2 = createMockMonthlyPlanPhase(rate2);
+        BillingEvent event2 = createBillingEvent(sub.getId(), buildDateTime(2011, 10, 15), plan1, phase2, 15);
         events.add(event2);
 
         InvoiceItemList existingInvoiceItems = new InvoiceItemList();
@@ -197,8 +192,8 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         BigDecimal numberOfCyclesEvent2 = TWO;
 
         BigDecimal expectedValue;
-        expectedValue = numberOfCyclesEvent1.multiply(FIVE);
-        expectedValue = expectedValue.add(numberOfCyclesEvent2.multiply(TEN));
+        expectedValue = numberOfCyclesEvent1.multiply(rate1);
+        expectedValue = expectedValue.add(numberOfCyclesEvent2.multiply(rate2));
         expectedValue = expectedValue.setScale(NUMBER_OF_DECIMALS);
 
         assertEquals(invoice.getTotalAmount(), expectedValue);
@@ -208,28 +203,22 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
     public void testOnePlan_ThreeMonthlyPhases_ChangeEOT() {
         BillingEventSet events = new BillingEventSet();
 
-        MockCatalog catalog = new MockCatalog();
-        Plan plan1 = catalog.getCurrentPlans()[0];
-        PlanPhase phase1 = plan1.getAllPhases()[0];
-
+        Plan plan1 = new MockPlan();
+        BigDecimal rate1 = FIVE;
+        PlanPhase phase1 = createMockMonthlyPlanPhase(rate1);
 
         Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
-        BillingEvent event1 = new DefaultBillingEvent(sub, buildDateTime(2011, 9, 1),
-        										plan1,phase1,
-                                               new InternationalPriceMock(ZERO),new InternationalPriceMock(FIVE), BillingPeriod.MONTHLY,
-                                               1, BillingModeType.IN_ADVANCE,"Test");
+        BillingEvent event1 = createBillingEvent(sub.getId(), buildDateTime(2011, 9, 1), plan1, phase1, 1);
         events.add(event1);
 
-        BillingEvent event2 = new DefaultBillingEvent(sub, buildDateTime(2011, 10, 1),
-												plan1,phase1, //technically should be a different phase but it doesn't impact the test
-                                               new InternationalPriceMock(ZERO),new InternationalPriceMock(TEN), BillingPeriod.MONTHLY,
-                                               1, BillingModeType.IN_ADVANCE,"Test");
+        BigDecimal rate2 = TEN;
+        PlanPhase phase2 = createMockMonthlyPlanPhase(rate2);
+        BillingEvent event2 = createBillingEvent(sub.getId(), buildDateTime(2011, 10, 1), plan1, phase2, 1);
         events.add(event2);
 
-        BillingEvent event3 = new DefaultBillingEvent(sub, buildDateTime(2011, 11, 1),
-												plan1,phase1, //technically should be a different phase but it doesn't impact the test
-                                               new InternationalPriceMock(ZERO),new InternationalPriceMock(THIRTY), BillingPeriod.MONTHLY,
-                                               1, BillingModeType.IN_ADVANCE,"Test");
+        BigDecimal rate3 = THIRTY;
+        PlanPhase phase3 = createMockMonthlyPlanPhase(rate3);
+        BillingEvent event3 = createBillingEvent(sub.getId(), buildDateTime(2011, 11, 1), plan1, phase3, 1);
         events.add(event3);
 
         InvoiceItemList existingInvoiceItems = new InvoiceItemList();
@@ -239,7 +228,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
 
         assertNotNull(invoice);
         assertEquals(invoice.getNumberOfItems(), 3);
-        assertEquals(invoice.getTotalAmount(), FIVE.add(TEN).add(TWO.multiply(THIRTY)).setScale(NUMBER_OF_DECIMALS));
+        assertEquals(invoice.getTotalAmount(), rate1.add(rate2).add(TWO.multiply(rate3)).setScale(NUMBER_OF_DECIMALS));
     }
 
     @Test
@@ -248,30 +237,24 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
 
         Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
         DateTime startDate = buildDateTime(2011, 9, 1);
-        
-        MockCatalog catalog = new MockCatalog();
-        Plan plan1 = catalog.getCurrentPlans()[0];
-        PlanPhase phase1 = plan1.getAllPhases()[0];
-        
+
+        Plan plan1 = new MockPlan();
         BigDecimal rate = FIVE;
-        BillingEvent event1 = new DefaultBillingEvent(sub, startDate,
-        										plan1,phase1,
-                                               new InternationalPriceMock(ZERO),new InternationalPriceMock(rate), BillingPeriod.MONTHLY,
-                                               1, BillingModeType.IN_ADVANCE,"Test");
+        PlanPhase phase1 = createMockMonthlyPlanPhase(rate);
+        
+        BillingEvent event1 = createBillingEvent(sub.getId(), startDate, plan1, phase1, 1);
         events.add(event1);
 
+        DateTime targetDate = buildDateTime(2011, 12, 1);
+        UUID accountId = UUID.randomUUID();
+        Invoice invoice1 = generator.generateInvoice(accountId, events, null, targetDate, Currency.USD);
         InvoiceItemList existingInvoiceItems = new InvoiceItemList();
-        InvoiceItem invoiceItem = new DefaultInvoiceItem(UUID.randomUUID(), sub.getId(), startDate, buildDateTime(2012, 1, 1), "",
-                                                 rate.multiply(FOUR), rate, Currency.USD);
-        existingInvoiceItems.add(invoiceItem);
+        existingInvoiceItems.addAll(invoice1.getInvoiceItems());
 
-        DateTime targetDate = buildDateTime(2011, 12, 3);
-        UUID accountId = UUID.randomUUID();
-        Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
+        targetDate = buildDateTime(2011, 12, 3);
+        Invoice invoice2 = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
 
-        assertNotNull(invoice);
-        assertEquals(invoice.getNumberOfItems(), 0);
-        assertEquals(invoice.getTotalAmount(), ZERO);
+        assertNull(invoice2);
     }
 
     @Test
@@ -283,35 +266,44 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         // plan 5: addon to plan 2, with bill cycle alignment to plan; immediate cancellation
 
         UUID subscriptionId1 = UUID.randomUUID();
-        String planName1 = "Change from trial to discount with immediate cancellation";
-        String plan1PhaseName1 = "Trial"; String plan1PhaseName2 = "Discount"; String plan1phase3 = "Cancel";
+        UUID subscriptionId2 = UUID.randomUUID();
+        UUID subscriptionId3 = UUID.randomUUID();
+        UUID subscriptionId4 = UUID.randomUUID();
+        UUID subscriptionId5 = UUID.randomUUID();
+
+        Plan plan1 = new MockPlan("Change from trial to discount with immediate cancellation");
+        PlanPhase plan1Phase1 = createMockMonthlyPlanPhase(EIGHT, PhaseType.TRIAL);
+        PlanPhase plan1Phase2 = createMockMonthlyPlanPhase(TWELVE, PhaseType.DISCOUNT);
+        PlanPhase plan1Phase3 = createMockMonthlyPlanPhase();
         DateTime plan1StartDate = buildDateTime(2011, 1, 5);
         DateTime plan1PhaseChangeDate = buildDateTime(2011, 4, 5);
         DateTime plan1CancelDate = buildDateTime(2011, 4, 29);
 
-        UUID subscriptionId2 = UUID.randomUUID();
-        String planName2 = "Change phase from trial to discount to evergreen";
-        String plan2PhaseName1 = "Trial"; String plan2PhaseName2 = "Discount"; String plan2PhaseName3 = "Evergreen";
+        Plan plan2 = new MockPlan("Change phase from trial to discount to evergreen");
+        PlanPhase plan2Phase1 = createMockMonthlyPlanPhase(TWENTY, PhaseType.TRIAL);
+        PlanPhase plan2Phase2 = createMockMonthlyPlanPhase(THIRTY, PhaseType.DISCOUNT);
+        PlanPhase plan2Phase3 = createMockMonthlyPlanPhase(FORTY, PhaseType.EVERGREEN);
         DateTime plan2StartDate = buildDateTime(2011, 3, 10);
         DateTime plan2PhaseChangeToDiscountDate = buildDateTime(2011, 6, 10);
         DateTime plan2PhaseChangeToEvergreenDate = buildDateTime(2011, 9, 10);
 
-        UUID subscriptionId3 = UUID.randomUUID();
-        String planName3 = "Upgrade with immediate change, BCD = 31";
-        String plan3PhaseName1 = "Evergreen monthly"; String plan3PhaseName2 = "Evergreen annual";
+        Plan plan3 = new MockPlan("Upgrade with immediate change, BCD = 31");
+        PlanPhase plan3Phase1 = createMockMonthlyPlanPhase(TEN, PhaseType.EVERGREEN);
+        PlanPhase plan3Phase2 = createMockAnnualPlanPhase(ONE_HUNDRED, PhaseType.EVERGREEN);
         DateTime plan3StartDate = buildDateTime(2011, 5, 20);
         DateTime plan3UpgradeToAnnualDate = buildDateTime(2011, 7, 31);
 
-        UUID subscriptionId4 = UUID.randomUUID();
-        String planName4a = "Plan change effective EOT; plan 1";
-        String planName4b = "Plan change effective EOT; plan 2";
-        String plan4PhaseName = "Evergreen";
+        Plan plan4a = new MockPlan("Plan change effective EOT; plan 1");
+        Plan plan4b = new MockPlan("Plan change effective EOT; plan 2");
+        PlanPhase plan4aPhase1 = createMockMonthlyPlanPhase(FIFTEEN);
+        PlanPhase plan4bPhase1 = createMockMonthlyPlanPhase(TWENTY_FOUR);
+
         DateTime plan4StartDate = buildDateTime(2011, 6, 7);
         DateTime plan4ChangeOfPlanDate = buildDateTime(2011, 8, 7);
 
-        UUID subscriptionId5 = UUID.randomUUID();
-        String planName5 = "Add-on";
-        String plan5PhaseName1 = "Evergreen"; String plan5PhaseName2 = "Cancel";
+        Plan plan5 = new MockPlan("Add-on");
+        PlanPhase plan5Phase1 = createMockMonthlyPlanPhase(TWENTY);
+        PlanPhase plan5Phase2 = createMockMonthlyPlanPhase();
         DateTime plan5StartDate = buildDateTime(2011, 6, 21);
         DateTime plan5CancelDate = buildDateTime(2011, 10, 7);
 
@@ -320,7 +312,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         BillingEventSet events = new BillingEventSet();
 
         // on 1/5/2011, create subscription 1 (trial)
-        events.add(createBillingEvent(subscriptionId1, plan1StartDate, planName1, plan1PhaseName1, EIGHT, 5));
+        events.add(createBillingEvent(subscriptionId1, plan1StartDate, plan1, plan1Phase1, 5));
         expectedAmount = EIGHT;
         testInvoiceGeneration(events, invoiceItems, plan1StartDate, 1, expectedAmount);
 
@@ -333,12 +325,12 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         testInvoiceGeneration(events, invoiceItems, buildDateTime(2011, 3, 5), 1, expectedAmount);
 
         // on 3/10/2011, create subscription 2 (trial)
-        events.add(createBillingEvent(subscriptionId2, plan2StartDate, planName2, plan2PhaseName1, TWENTY, 10));
+        events.add(createBillingEvent(subscriptionId2, plan2StartDate, plan2, plan2Phase1, 10));
         expectedAmount = TWENTY;
         testInvoiceGeneration(events, invoiceItems, plan2StartDate, 1, expectedAmount);
 
         // on 4/5/2011, invoice subscription 1 (discount)
-        events.add(createBillingEvent(subscriptionId1, plan1PhaseChangeDate, planName1, plan1PhaseName2, TWELVE, 5));
+        events.add(createBillingEvent(subscriptionId1, plan1PhaseChangeDate, plan1, plan1Phase2, 5));
         expectedAmount = TWELVE;
         testInvoiceGeneration(events, invoiceItems, plan1PhaseChangeDate, 1, expectedAmount);
 
@@ -347,7 +339,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         testInvoiceGeneration(events, invoiceItems, buildDateTime(2011, 4, 10), 1, expectedAmount);
 
         // on 4/29/2011, cancel subscription 1
-        events.add(createBillingEvent(subscriptionId1, plan1CancelDate, planName1, plan1phase3, ZERO, 5));
+        events.add(createBillingEvent(subscriptionId1, plan1CancelDate, plan1, plan1Phase3, 5));
         expectedAmount = TWELVE.multiply(SIX.divide(THIRTY, NUMBER_OF_DECIMALS, ROUNDING_METHOD)).negate().setScale(NUMBER_OF_DECIMALS);
         testInvoiceGeneration(events, invoiceItems, plan1CancelDate, 2, expectedAmount);
 
@@ -356,17 +348,17 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         testInvoiceGeneration(events, invoiceItems, buildDateTime(2011, 5, 10), 1, expectedAmount);
 
         // on 5/20/2011, create subscription 3 (monthly)
-        events.add(createBillingEvent(subscriptionId3, plan3StartDate, planName3, plan3PhaseName1, TEN, 20));
+        events.add(createBillingEvent(subscriptionId3, plan3StartDate, plan3, plan3Phase1, 20));
         expectedAmount = TEN;
         testInvoiceGeneration(events, invoiceItems, plan3StartDate, 1, expectedAmount);
 
         // on 6/7/2011, create subscription 4
-        events.add(createBillingEvent(subscriptionId4, plan4StartDate, planName4a, plan4PhaseName, FIFTEEN, 7));
+        events.add(createBillingEvent(subscriptionId4, plan4StartDate, plan4a, plan4aPhase1, 7));
         expectedAmount = FIFTEEN;
         testInvoiceGeneration(events, invoiceItems, plan4StartDate, 1, expectedAmount);
 
         // on 6/10/2011, invoice subscription 2 (discount)
-        events.add(createBillingEvent(subscriptionId2, plan2PhaseChangeToDiscountDate, planName2, plan2PhaseName2, THIRTY, 10));
+        events.add(createBillingEvent(subscriptionId2, plan2PhaseChangeToDiscountDate, plan2, plan2Phase2, 10));
         expectedAmount = THIRTY;
         testInvoiceGeneration(events, invoiceItems, plan2PhaseChangeToDiscountDate, 1, expectedAmount);
 
@@ -375,7 +367,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         testInvoiceGeneration(events, invoiceItems, buildDateTime(2011, 6, 20), 1, expectedAmount);
 
         // on 6/21/2011, create add-on (subscription 5)
-        events.add(createBillingEvent(subscriptionId5, plan5StartDate, planName5, plan5PhaseName1, TWENTY, 10));
+        events.add(createBillingEvent(subscriptionId5, plan5StartDate, plan5, plan5Phase1, 10));
         expectedAmount = TWENTY.multiply(NINETEEN.divide(THIRTY, NUMBER_OF_DECIMALS, ROUNDING_METHOD)).setScale(NUMBER_OF_DECIMALS);
         testInvoiceGeneration(events, invoiceItems, plan5StartDate, 1, expectedAmount);
 
@@ -392,14 +384,14 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         testInvoiceGeneration(events, invoiceItems, buildDateTime(2011, 7, 20), 1, expectedAmount);
 
         // on 7/31/2011, convert subscription 3 to annual
-        events.add(createAnnualBillingEvent(subscriptionId3, plan3UpgradeToAnnualDate, planName3, plan3PhaseName2, ONE_HUNDRED, 31));
+        events.add(createBillingEvent(subscriptionId3, plan3UpgradeToAnnualDate, plan3, plan3Phase2, 31));
         expectedAmount = ONE_HUNDRED.subtract(TEN);
         expectedAmount = expectedAmount.add(TEN.multiply(ELEVEN.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD)));
         expectedAmount = expectedAmount.setScale(NUMBER_OF_DECIMALS);
         testInvoiceGeneration(events, invoiceItems, plan3UpgradeToAnnualDate, 3, expectedAmount);
 
         // on 8/7/2011, invoice subscription 4 (plan 2)
-        events.add(createBillingEvent(subscriptionId4, plan4ChangeOfPlanDate, planName4b, plan4PhaseName, TWENTY_FOUR, 7));
+        events.add(createBillingEvent(subscriptionId4, plan4ChangeOfPlanDate, plan4b, plan4bPhase1, 7));
         expectedAmount = TWENTY_FOUR;
         testInvoiceGeneration(events, invoiceItems, plan4ChangeOfPlanDate, 1, expectedAmount);
 
@@ -412,12 +404,12 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         testInvoiceGeneration(events, invoiceItems, buildDateTime(2011, 9, 7), 1, expectedAmount);
 
         // on 9/10/2011, invoice plan 2 (evergreen), invoice subscription 5
-        events.add(createBillingEvent(subscriptionId2, plan2PhaseChangeToEvergreenDate, planName2, plan2PhaseName3, FORTY, 10));
+        events.add(createBillingEvent(subscriptionId2, plan2PhaseChangeToEvergreenDate, plan2, plan2Phase3, 10));
         expectedAmount = FORTY.add(TWENTY);
         testInvoiceGeneration(events, invoiceItems, plan2PhaseChangeToEvergreenDate, 2, expectedAmount);
 
         // on 10/7/2011, invoice subscription 4 (plan 2), cancel subscription 5
-        events.add(createBillingEvent(subscriptionId5, plan5CancelDate, planName5, plan5PhaseName2, ZERO, 10));
+        events.add(createBillingEvent(subscriptionId5, plan5CancelDate, plan5, plan5Phase2, 10));
         expectedAmount = TWENTY_FOUR.add(TWENTY.multiply(THREE.divide(THIRTY)).negate().setScale(NUMBER_OF_DECIMALS));
         testInvoiceGeneration(events, invoiceItems, plan5CancelDate, 3, expectedAmount);
 
@@ -426,37 +418,175 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         testInvoiceGeneration(events, invoiceItems, buildDateTime(2011, 10, 10), 1, expectedAmount);
     }
 
-    private DefaultBillingEvent createBillingEvent(UUID subscriptionId, DateTime startDate, String planName, String planPhaseName,
-                                            BigDecimal rate, int billCycleDay) {
-        MockCatalog catalog = new MockCatalog();
-        Plan plan = catalog.getCurrentPlans()[0];
-        PlanPhase phase = plan.getAllPhases()[0];
-        Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
-        return new DefaultBillingEvent(sub, startDate, plan, phase,
-                new InternationalPriceMock(ZERO),new InternationalPriceMock(rate), BillingPeriod.MONTHLY,
-                                billCycleDay, BillingModeType.IN_ADVANCE,"Test");
+    @Test
+    public void testZeroDollarEvents() {
+        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);
+
+        assertEquals(invoice.getNumberOfItems(), 1);
     }
 
-    private DefaultBillingEvent createAnnualBillingEvent(UUID subscriptionId, DateTime startDate, String planName, String planPhaseName,
-                                                  BigDecimal rate, int billCycleDay) {
-        MockCatalog catalog = new MockCatalog();
-        Plan plan = catalog.getCurrentPlans()[0];
-        PlanPhase phase = plan.getAllPhases()[0];
-        Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
-        return new DefaultBillingEvent(sub, startDate, plan, phase,
-                new InternationalPriceMock(ZERO),new InternationalPriceMock(rate), BillingPeriod.ANNUAL,
-                                billCycleDay, BillingModeType.IN_ADVANCE,"Test");
+    @Test
+    public void testEndDateIsCorrect() {
+        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);
+
+        // 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());
     }
 
-    private void testInvoiceGeneration(BillingEventSet events, InvoiceItemList existingInvoiceItems, DateTime targetDate, int expectedNumberOfItems, BigDecimal expectedAmount) {
+    @Test
+    private void testFixedPriceLifeCycle() {
+        UUID accountId = UUID.randomUUID();
+        Subscription subscription = new MockSubscription();
+        Plan plan = new MockPlan("plan 1");
+        MockInternationalPrice zeroPrice = new MockInternationalPrice(new DefaultPrice(ZERO, Currency.USD));
+        MockInternationalPrice cheapPrice = new MockInternationalPrice(new DefaultPrice(ONE, Currency.USD));
+
+        PlanPhase phase1 = new MockPlanPhase(null, zeroPrice, BillingPeriod.NO_BILLING_PERIOD, PhaseType.TRIAL);
+        PlanPhase phase2 = new MockPlanPhase(cheapPrice, null, BillingPeriod.MONTHLY, PhaseType.DISCOUNT);
+
+        DateTime changeDate = new DateTime("2012-04-1T00:00:00.000-08:00");
+
+        BillingEventSet events = new BillingEventSet();
+
+        BillingEvent event1 = new DefaultBillingEvent(subscription, new DateTime("2012-01-1T00:00:00.000-08:00"),
+                                                      plan, phase1,
+                                                      zeroPrice, null, BillingPeriod.NO_BILLING_PERIOD, 1,
+                                                      BillingModeType.IN_ADVANCE, "Test Event 1",
+                                                      SubscriptionTransitionType.CREATE);
+
+        BillingEvent event2 = new DefaultBillingEvent(subscription, changeDate,
+                                                      plan, phase2,
+                                                      zeroPrice, null, BillingPeriod.NO_BILLING_PERIOD, 1,
+                                                      BillingModeType.IN_ADVANCE, "Test Event 2",
+                                                      SubscriptionTransitionType.PHASE);
+
+        events.add(event2);
+        events.add(event1);
+        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);
+   }
+
+    private MockPlanPhase createMockMonthlyPlanPhase() {
+        return new MockPlanPhase(null, null, BillingPeriod.MONTHLY);
+    }
+
+    private MockPlanPhase createMockMonthlyPlanPhase(@Nullable final BigDecimal recurringRate) {
+        return new MockPlanPhase(new MockInternationalPrice(new DefaultPrice(recurringRate, Currency.USD)),
+                                 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 createMockAnnualPlanPhase(final BigDecimal recurringRate, final PhaseType phaseType) {
+        return new MockPlanPhase(new MockInternationalPrice(new DefaultPrice(recurringRate, Currency.USD)),
+                                 null, BillingPeriod.ANNUAL, phaseType);
+    }
+
+    private DefaultBillingEvent createBillingEvent(final UUID subscriptionId, final DateTime startDate,
+                                                   final Plan plan, final PlanPhase planPhase, final int billCycleDay) {
+        Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(subscriptionId));
+
+        return new DefaultBillingEvent(sub, startDate, plan, planPhase,
+                                       planPhase.getFixedPrice(),
+                                       planPhase.getRecurringPrice(), planPhase.getBillingPeriod(),
+                                       billCycleDay, BillingModeType.IN_ADVANCE,"Test", SubscriptionTransitionType.CREATE);
+    }
+
+    private void testInvoiceGeneration(final BillingEventSet events, final InvoiceItemList existingInvoiceItems,
+                                       final DateTime targetDate, final int expectedNumberOfItems,
+                                       final BigDecimal expectedAmount) {
         Currency currency = Currency.USD;
         UUID accountId = UUID.randomUUID();
         Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, currency);
-        existingInvoiceItems.addAll(invoice.getItems());
         assertNotNull(invoice);
         assertEquals(invoice.getNumberOfItems(), expectedNumberOfItems);
+
+        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/annual/DoubleProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/DoubleProRationTests.java
index 84bf795..64fa95c 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/DoubleProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/DoubleProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class DoubleProRationTests extends ProRationInAdvanceTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/GenericProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/GenericProRationTests.java
index f612f10..8d0eb06 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/GenericProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/GenericProRationTests.java
@@ -22,7 +22,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class GenericProRationTests extends GenericProRationTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/LeadingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/LeadingProRationTests.java
index f44876e..a009fba 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/LeadingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/LeadingProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class LeadingProRationTests extends ProRationInAdvanceTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/ProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/ProRationTests.java
index 274a8e7..f882005 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/ProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/ProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class ProRationTests extends ProRationInAdvanceTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/TrailingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/TrailingProRationTests.java
index 0689c69..e0216b5 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/TrailingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/TrailingProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class TrailingProRationTests extends ProRationInAdvanceTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/GenericProRationTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/GenericProRationTestBase.java
index d4e4c24..60892e6 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/GenericProRationTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/GenericProRationTestBase.java
@@ -22,7 +22,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public abstract class GenericProRationTestBase extends ProRationInAdvanceTestBase {
     /**
      * used for testing cancellation in less than a single billing period
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/DoubleProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/DoubleProRationTests.java
index 4799e40..240b8aa 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/DoubleProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/DoubleProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class DoubleProRationTests extends ProRationInAdvanceTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/GenericProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/GenericProRationTests.java
index eb3b1c9..8b40db8 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/GenericProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/GenericProRationTests.java
@@ -22,7 +22,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class GenericProRationTests extends GenericProRationTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/LeadingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/LeadingProRationTests.java
index 5db6911..998e566 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/LeadingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/LeadingProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class LeadingProRationTests extends ProRationInAdvanceTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/ProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/ProRationTests.java
index 13c75df..c3748d1 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/ProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/ProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class ProRationTests extends ProRationInAdvanceTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/TrailingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/TrailingProRationTests.java
index 9a8e635..6a5e5ef 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/TrailingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/TrailingProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class TrailingProRationTests extends ProRationInAdvanceTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java
index 14392a8..18bd096 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java
@@ -21,7 +21,7 @@ import com.ning.billing.invoice.model.InAdvanceBillingMode;
 import com.ning.billing.invoice.tests.ProRationTestBase;
 import org.testng.annotations.Test;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public abstract class ProRationInAdvanceTestBase extends ProRationTestBase {
     @Override
     protected BillingMode getBillingMode() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/DoubleProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/DoubleProRationTests.java
index 68e29fd..c1d6085 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/DoubleProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/DoubleProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class DoubleProRationTests extends ProRationInAdvanceTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/GenericProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/GenericProRationTests.java
index 8306716..c4237a6 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/GenericProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/GenericProRationTests.java
@@ -22,7 +22,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class GenericProRationTests extends GenericProRationTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/LeadingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/LeadingProRationTests.java
index fe614a9..04ec683 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/LeadingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/LeadingProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class LeadingProRationTests extends ProRationInAdvanceTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/ProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/ProRationTests.java
index c99aa5c..e13db0d 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/ProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/ProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class ProRationTests extends ProRationInAdvanceTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/TrailingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/TrailingProRationTests.java
index 89e138e..8f63010 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/TrailingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/TrailingProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
 
 import java.math.BigDecimal;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class TrailingProRationTests extends ProRationInAdvanceTestBase {
     @Override
     protected BillingPeriod getBillingPeriod() {
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 9de01bd..b38d076 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
@@ -28,7 +28,7 @@ import java.math.BigDecimal;
 
 import static org.testng.Assert.assertEquals;
 
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
 public class ValidationProRationTests extends ProRationTestBase {
     protected BillingPeriod getBillingPeriod() {
         return BillingPeriod.MONTHLY;
diff --git a/invoice/src/test/resources/log4j.xml b/invoice/src/test/resources/log4j.xml
new file mode 100644
index 0000000..33b9662
--- /dev/null
+++ b/invoice/src/test/resources/log4j.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+    <appender name="stdout" class="org.apache.log4j.ConsoleAppender">
+        <param name="Target" value="System.out"/>
+        <layout class="org.apache.log4j.PatternLayout">
+            <param name="ConversionPattern" value="%p	%d{ISO8601}	%X{trace}	%t	%c	%m%n"/>
+        </layout>
+    </appender>
+
+
+    <logger name="com.ning.billing.invoice">
+        <level value="info"/>
+    </logger>
+
+    <root>
+        <priority value="info"/>
+        <appender-ref ref="stdout"/>
+    </root>
+</log4j:configuration>

payment/pom.xml 2(+1 -1)

diff --git a/payment/pom.xml b/payment/pom.xml
index 5baa4d4..3e050f0 100644
--- a/payment/pom.xml
+++ b/payment/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.3-SNAPSHOT</version>
+        <version>0.1.5-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-payment</artifactId>
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 5a0e2f1..5d9f58f 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
@@ -32,6 +32,7 @@ import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountUserApi;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.invoice.model.DefaultInvoicePayment;
 import com.ning.billing.payment.dao.PaymentDao;
 import com.ning.billing.payment.provider.PaymentProviderPlugin;
 import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
@@ -175,7 +176,7 @@ public class DefaultPaymentApi implements PaymentApi {
         for (String invoiceId : invoiceIds) {
             Invoice invoice = invoicePaymentApi.getInvoice(UUID.fromString(invoiceId));
 
-            if (invoice.getAmountOutstanding().compareTo(BigDecimal.ZERO) == 0 ) {
+            if (invoice.getBalance().compareTo(BigDecimal.ZERO) == 0 ) {
             // TODO: send a notification that invoice was ignored?
                 log.info("Received invoice for payment with outstanding amount of 0 {} ", invoice);
             }
@@ -222,12 +223,12 @@ public class DefaultPaymentApi implements PaymentApi {
                     }
                 }
 
-                invoicePaymentApi.notifyOfPaymentAttempt(new InvoicePayment(invoice.getId(),
-                                                                     paymentInfo == null ? null : paymentInfo.getAmount(),
-//                                                                   paymentInfo.getRefundAmount(), TODO
-                                                                     paymentInfo == null ? null : invoice.getCurrency(),
-                                                                     paymentAttempt.getPaymentAttemptId(),
-                                                                     paymentAttempt.getPaymentAttemptDate()));
+                invoicePaymentApi.notifyOfPaymentAttempt(new DefaultInvoicePayment(paymentAttempt.getPaymentAttemptId(),
+                                                                                   invoice.getId(),
+                                                                                   paymentAttempt.getPaymentAttemptDate(),
+                                                                                   paymentInfo == null ? null : paymentInfo.getAmount(),
+//                                                                                 paymentInfo.getRefundAmount(), TODO
+                                                                                   paymentInfo == null ? null : invoice.getCurrency()));
 
             }
         }
@@ -258,4 +259,14 @@ public class DefaultPaymentApi implements PaymentApi {
         throw new UnsupportedOperationException();
     }
 
+    @Override
+    public List<PaymentInfo> getPaymentInfo(List<String> invoiceIds) {
+        return paymentDao.getPaymentInfo(invoiceIds);
+    }
+
+    @Override
+    public PaymentAttempt getPaymentAttemptForInvoiceId(String invoiceId) {
+        return paymentDao.getPaymentAttemptForInvoiceId(invoiceId);
+    }
+
 }
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.java b/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.java
index 9cc46a4..9a96631 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.java
@@ -16,6 +16,7 @@
 
 package com.ning.billing.payment.dao;
 
+import java.util.List;
 import java.util.UUID;
 
 import org.skife.jdbi.v2.IDBI;
@@ -44,6 +45,12 @@ public class DefaultPaymentDao implements PaymentDao {
     }
 
     @Override
+    public PaymentAttempt createPaymentAttempt(PaymentAttempt paymentAttempt) {
+        sqlDao.insertPaymentAttempt(paymentAttempt);
+        return paymentAttempt;
+    }
+
+    @Override
     public PaymentAttempt createPaymentAttempt(Invoice invoice) {
         final PaymentAttempt paymentAttempt = new PaymentAttempt(UUID.randomUUID(), invoice);
 
@@ -67,15 +74,13 @@ public class DefaultPaymentDao implements PaymentDao {
     }
 
     @Override
-    public PaymentAttempt getPaymentAttemptById(UUID paymentAttemptId) {
-        // TODO Auto-generated method stub
-        return null;
+    public List<PaymentInfo> getPaymentInfo(List<String> invoiceIds) {
+        return sqlDao.getPaymentInfos(invoiceIds);
     }
 
     @Override
-    public void updatePaymentAttempt(PaymentAttempt updatedPaymentAttempt) {
-        // TODO Auto-generated method stub
-
+    public List<PaymentAttempt> getPaymentAttemptsForInvoiceIds(List<String> invoiceIds) {
+        return sqlDao.getPaymentAttemptsForInvoiceIds(invoiceIds);
     }
 
 }
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java
index a70098b..4d4a411 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java
@@ -16,6 +16,7 @@
 
 package com.ning.billing.payment.dao;
 
+import java.util.List;
 import java.util.UUID;
 
 import com.ning.billing.invoice.api.Invoice;
@@ -25,10 +26,12 @@ import com.ning.billing.payment.api.PaymentInfo;
 public interface PaymentDao {
 
     PaymentAttempt createPaymentAttempt(Invoice invoice);
+    PaymentAttempt createPaymentAttempt(PaymentAttempt paymentAttempt);
 
     void savePaymentInfo(PaymentInfo right);
 
     PaymentAttempt getPaymentAttemptForPaymentId(String paymentId);
+    List<PaymentAttempt> getPaymentAttemptsForInvoiceIds(List<String> invoiceIds);
 
     void updatePaymentAttemptWithPaymentId(UUID paymentAttemptId, String paymentId);
 
@@ -36,8 +39,6 @@ public interface PaymentDao {
 
     void updatePaymentInfo(String paymentMethodType, String paymentId, String cardType, String cardCountry);
 
-    PaymentAttempt getPaymentAttemptById(UUID paymentAttemptId);
-
-    void updatePaymentAttempt(PaymentAttempt updatedPaymentAttempt);
+    List<PaymentInfo> getPaymentInfo(List<String> invoiceIds);
 
 }
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java
index 972ee64..13b5f51 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java
@@ -21,6 +21,7 @@ import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Timestamp;
 import java.util.Date;
+import java.util.List;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
@@ -37,6 +38,7 @@ import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
 import org.skife.jdbi.v2.tweak.ResultSetMapper;
+import org.skife.jdbi.v2.unstable.BindIn;
 
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.payment.api.PaymentAttempt;
@@ -55,6 +57,10 @@ public interface PaymentSqlDao extends Transactional<PaymentSqlDao>, CloseMe, Tr
     @Mapper(PaymentAttemptMapper.class)
     PaymentAttempt getPaymentAttemptForInvoiceId(@Bind("invoice_id") String invoiceId);
 
+    @SqlQuery
+    @Mapper(PaymentAttemptMapper.class)
+    List<PaymentAttempt> getPaymentAttemptsForInvoiceIds(@BindIn("invoiceIds") List<String> invoiceIds);
+
     @SqlUpdate
     void updatePaymentAttemptWithPaymentId(@Bind("payment_attempt_id") String paymentAttemptId,
                                            @Bind("payment_id") String paymentId);
@@ -65,6 +71,10 @@ public interface PaymentSqlDao extends Transactional<PaymentSqlDao>, CloseMe, Tr
                            @Bind("card_type") String cardType,
                            @Bind("card_country") String cardCountry);
 
+    @SqlQuery
+    @Mapper(PaymentInfoMapper.class)
+    List<PaymentInfo> getPaymentInfos(@BindIn("invoiceIds") List<String> invoiceIds);
+
     @SqlUpdate
     void insertPaymentInfo(@Bind(binder = PaymentInfoBinder.class) PaymentInfo paymentInfo);
 
diff --git a/payment/src/main/java/com/ning/billing/payment/PaymentAttempt.java b/payment/src/main/java/com/ning/billing/payment/PaymentAttempt.java
index 62fce18..1c8e9f3 100644
--- a/payment/src/main/java/com/ning/billing/payment/PaymentAttempt.java
+++ b/payment/src/main/java/com/ning/billing/payment/PaymentAttempt.java
@@ -41,7 +41,7 @@ public class PaymentAttempt {
         this.paymentAttemptId = paymentAttemptId;
         this.accountId = invoice.getAccountId();
         this.invoiceId = invoice.getId();
-        this.paymentAttemptAmount = invoice.getAmountOutstanding();
+        this.paymentAttemptAmount = invoice.getBalance();
         this.paymentAttemptDate = new DateTime(DateTimeZone.UTC);
         this.retryCount = retryCount;
         this.nextRetryDate = nextRetryDate;
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 466f297..2c86e3b 100644
--- a/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java
+++ b/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java
@@ -31,17 +31,15 @@ import com.ning.billing.payment.api.Either;
 import com.ning.billing.payment.api.PaymentApi;
 import com.ning.billing.payment.api.PaymentError;
 import com.ning.billing.payment.api.PaymentInfo;
-import com.ning.billing.payment.provider.PaymentProviderPlugin;
 import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
-import com.ning.billing.util.eventbus.EventBus;
-import com.ning.billing.util.eventbus.EventBus.EventBusException;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
 
 public class RequestProcessor {
     public static final String PAYMENT_PROVIDER_KEY = "paymentProvider";
     private final AccountUserApi accountUserApi;
     private final PaymentApi paymentApi;
-    private final PaymentProviderPluginRegistry pluginRegistry;
-    private final EventBus eventBus;
+    private final Bus eventBus;
 
     private static final Logger log = LoggerFactory.getLogger(RequestProcessor.class);
 
@@ -49,10 +47,9 @@ public class RequestProcessor {
     public RequestProcessor(AccountUserApi accountUserApi,
                             PaymentApi paymentApi,
                             PaymentProviderPluginRegistry pluginRegistry,
-                            EventBus eventBus) {
+                            Bus eventBus) {
         this.accountUserApi = accountUserApi;
         this.paymentApi = paymentApi;
-        this.pluginRegistry = pluginRegistry;
         this.eventBus = eventBus;
     }
 
@@ -81,20 +78,4 @@ public class RequestProcessor {
             throw new RuntimeException(ex);
         }
     }
-
-    @Subscribe
-    public void receivePaymentInfoRequest(PaymentInfoRequest paymentInfoRequest) throws EventBusException {
-        final Account account = accountUserApi.getAccountById(paymentInfoRequest.getAccountId());
-        if (account == null) {
-            log.info("could not process payment info request: could not find a valid account for event {}", paymentInfoRequest);
-        }
-        else {
-            final String paymentProviderName = account.getFieldValue(PAYMENT_PROVIDER_KEY);
-            final PaymentProviderPlugin plugin = pluginRegistry.getPlugin(paymentProviderName);
-
-            Either<PaymentError, PaymentInfo> result = plugin.getPaymentInfo(paymentInfoRequest.getPaymentId());
-
-            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
new file mode 100644
index 0000000..49acce3
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/RetryService.java
@@ -0,0 +1,90 @@
+/*
+ * 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;
+
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+
+import com.google.inject.Inject;
+import com.ning.billing.lifecycle.KillbillService;
+import com.ning.billing.lifecycle.LifecycleHandlerType;
+import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
+import com.ning.billing.payment.setup.PaymentConfig;
+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.NotificationQueueAlreadyExists;
+import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
+
+public class RetryService implements KillbillService {
+    public static final String SERVICE_NAME = "retry-service";
+    public static final String QUEUE_NAME = "retry-events";
+
+    private final NotificationQueueService notificationQueueService;
+    private final PaymentConfig config;
+    private NotificationQueue retryQueue;
+
+    @Inject
+    public RetryService(NotificationQueueService notificationQueueService, PaymentConfig config) {
+        this.notificationQueueService = notificationQueueService;
+        this.config = config;
+    }
+
+    @Override
+    public String getName() {
+        return SERVICE_NAME;
+    }
+
+    @LifecycleHandlerType(LifecycleLevel.INIT_SERVICE)
+    public void initialize() throws NotificationQueueAlreadyExists {
+        retryQueue = notificationQueueService.createNotificationQueue(SERVICE_NAME, QUEUE_NAME, new NotificationQueueHandler() {
+            @Override
+            public void handleReadyNotification(String notificationKey) {
+                retry(notificationKey);
+            }
+        },
+        config);
+    }
+
+    @LifecycleHandlerType(LifecycleLevel.START_SERVICE)
+    public void start() {
+        retryQueue.startQueue();
+    }
+
+    @LifecycleHandlerType(LifecycleLevel.STOP_SERVICE)
+    public void stop() {
+        if (retryQueue != null) {
+            retryQueue.stopQueue();
+         }
+    }
+
+    public void scheduleRetry(Transmogrifier transactionalDao, PaymentAttempt paymentAttempt, DateTime timeOfRetry) {
+        final String id = paymentAttempt.getPaymentAttemptId().toString();
+
+        NotificationKey key = new NotificationKey() {
+            @Override
+            public String toString() {
+                return id;
+            }
+        };
+        retryQueue.recordFutureNotificationFromTransaction(transactionalDao, timeOfRetry, key);
+    }
+
+    private void retry(String paymentAttemptId) {
+        // TODO
+    }
+}
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 83cb89b..af0b3d8 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
@@ -23,7 +23,9 @@ import org.skife.config.Default;
 import org.skife.config.DefaultNull;
 import org.skife.config.TimeSpan;
 
-public interface PaymentConfig {
+import com.ning.billing.util.notificationq.NotificationConfig;
+
+public interface PaymentConfig extends NotificationConfig {
     @Config("killbill.payment.provider.default")
     @DefaultNull
     public String getDefaultPaymentProvider();
@@ -32,14 +34,20 @@ public interface PaymentConfig {
     @DefaultNull
     public List<String> getPaymentRetryDays();
 
-    @Config("killbill.payment.retry.pause")
-    // payment retry job is off by default
-    @DefaultNull
-    TimeSpan getPaymentRetrySchedulePause();
+    @Config("killbill.payment.dao.claim.time")
+    @Default("60000")
+    public long getDaoClaimTimeMs();
+
+    @Config("killbill.payment.dao.ready.max")
+    @Default("10")
+    public int getDaoMaxReadyEvents();
+
+    @Config("killbill.payment.engine.notifications.sleep")
+    @Default("500")
+    public long getNotificationSleepTimeMs();
 
-    @Config("killbill.payment.retry.claim.timeout")
-    // if payment retry job is on, then retry abandoned payment attempts after some period of time
-    @Default("1h")
-    TimeSpan getPaymentRetryClaimTimeout();
+    @Config("killbill.payment.engine.events.off")
+    @Default("false")
+    public boolean isNotificationProcessingOff();
 
 }
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 3c05c13..b8af029 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
@@ -21,6 +21,8 @@ import java.util.Properties;
 import org.skife.config.ConfigurationObjectFactory;
 
 import com.google.inject.AbstractModule;
+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.dao.DefaultPaymentDao;
@@ -45,6 +47,10 @@ public class PaymentModule extends AbstractModule {
     protected void installPaymentProviderPlugins(PaymentConfig config) {
     }
 
+    protected void installRetryEngine() {
+        bind(RetryService.class).asEagerSingleton();
+    }
+
     @Override
     protected void configure() {
         final ConfigurationObjectFactory factory = new ConfigurationObjectFactory(props);
@@ -53,6 +59,8 @@ public class PaymentModule extends AbstractModule {
         bind(PaymentConfig.class).toInstance(paymentConfig);
         bind(PaymentProviderPluginRegistry.class).asEagerSingleton();
         bind(PaymentApi.class).to(DefaultPaymentApi.class).asEagerSingleton();
+        bind(RequestProcessor.class).asEagerSingleton();
+        bind(PaymentService.class).asEagerSingleton();
         installPaymentProviderPlugins(paymentConfig);
         installPaymentDao();
     }
diff --git a/payment/src/main/java/com/ning/billing/payment/setup/PaymentService.java b/payment/src/main/java/com/ning/billing/payment/setup/PaymentService.java
new file mode 100644
index 0000000..5113068
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/setup/PaymentService.java
@@ -0,0 +1,57 @@
+/*
+ * 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.payment.setup;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+import com.ning.billing.lifecycle.KillbillService;
+import com.ning.billing.lifecycle.LifecycleHandlerType;
+import com.ning.billing.payment.RequestProcessor;
+import com.ning.billing.util.bus.Bus;
+
+public class PaymentService implements KillbillService {
+    private static final Logger log = LoggerFactory.getLogger(PaymentService.class);
+
+    private static final String SERVICE_NAME = "payment-service";
+
+    private final RequestProcessor requestProcessor;
+    private final Bus eventBus;
+
+    @Inject
+    public PaymentService(final RequestProcessor requestProcessor, final Bus eventBus) {
+        this.requestProcessor = requestProcessor;
+        this.eventBus = eventBus;
+    }
+
+    @Override
+    public String getName() {
+        return SERVICE_NAME;
+    }
+
+    @LifecycleHandlerType(LifecycleHandlerType.LifecycleLevel.REGISTER_EVENTS)
+    public void registerForNotifications() {
+        try {
+            eventBus.register(requestProcessor);
+        }
+        catch (Bus.EventBusException e) {
+            log.error("Unable to register with the EventBus!", e);
+        }
+    }
+
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/util/EventBusFuture.java b/payment/src/main/java/com/ning/billing/payment/util/EventBusFuture.java
index d82a9a7..3cbe802 100644
--- a/payment/src/main/java/com/ning/billing/payment/util/EventBusFuture.java
+++ b/payment/src/main/java/com/ning/billing/payment/util/EventBusFuture.java
@@ -20,11 +20,11 @@ import javax.annotation.Nullable;
 
 import com.google.common.eventbus.Subscribe;
 import com.google.common.util.concurrent.AbstractFuture;
-import com.ning.billing.util.eventbus.EventBus;
-import com.ning.billing.util.eventbus.EventBus.EventBusException;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
 
 public class EventBusFuture<T, V extends EventBusResponse<T>> extends AbstractFuture<V> {
-    public static <V, W extends EventBusRequest<V>, X extends EventBusResponse<V>> EventBusFuture<V, X> post(final EventBus eventBus, final W event) throws EventBusException {
+    public static <V, W extends EventBusRequest<V>, X extends EventBusResponse<V>> EventBusFuture<V, X> post(final Bus eventBus, final W event) throws EventBusException {
         final EventBusFuture<V, X> responseFuture = new EventBusFuture<V, X>(eventBus, event.getId());
 
         eventBus.register(responseFuture);
@@ -32,10 +32,10 @@ public class EventBusFuture<T, V extends EventBusResponse<T>> extends AbstractFu
         return responseFuture;
     }
 
-    private final EventBus eventBus;
+    private final Bus eventBus;
     private final T requestId;
 
-    private EventBusFuture(EventBus eventBus, T requestId) {
+    private EventBusFuture(Bus eventBus, T requestId) {
         this.eventBus = eventBus;
         this.requestId = requestId;
     }
diff --git a/payment/src/main/java/com/ning/billing/payment/util/EventBusRequest.java b/payment/src/main/java/com/ning/billing/payment/util/EventBusRequest.java
index b9dab5c..a895afc 100644
--- a/payment/src/main/java/com/ning/billing/payment/util/EventBusRequest.java
+++ b/payment/src/main/java/com/ning/billing/payment/util/EventBusRequest.java
@@ -16,8 +16,8 @@
 
 package com.ning.billing.payment.util;
 
-import com.ning.billing.util.eventbus.EventBusNotification;
+import com.ning.billing.util.bus.BusEvent;
 
-public interface EventBusRequest<T> extends EventBusNotification {
+public interface EventBusRequest<T> extends BusEvent {
     T getId();
 }
diff --git a/payment/src/main/java/com/ning/billing/payment/util/EventBusResponse.java b/payment/src/main/java/com/ning/billing/payment/util/EventBusResponse.java
index d8a70b1..ff5b62c 100644
--- a/payment/src/main/java/com/ning/billing/payment/util/EventBusResponse.java
+++ b/payment/src/main/java/com/ning/billing/payment/util/EventBusResponse.java
@@ -16,8 +16,8 @@
 
 package com.ning.billing.payment.util;
 
-import com.ning.billing.util.eventbus.EventBusNotification;
+import com.ning.billing.util.bus.BusEvent;
 
-public interface EventBusResponse<T> extends EventBusNotification {
+public interface EventBusResponse<T> extends BusEvent {
     T getRequestId();
 }
diff --git a/payment/src/main/resources/com/ning/billing/payment/dao/PaymentSqlDao.sql.stg b/payment/src/main/resources/com/ning/billing/payment/dao/PaymentSqlDao.sql.stg
index a62c366..aea2272 100644
--- a/payment/src/main/resources/com/ning/billing/payment/dao/PaymentSqlDao.sql.stg
+++ b/payment/src/main/resources/com/ning/billing/payment/dao/PaymentSqlDao.sql.stg
@@ -24,6 +24,7 @@ paymentInfoFields(prefix) ::= <<
     <prefix>payment_type,
     <prefix>status,
     <prefix>reference_id,
+    <prefix>payment_method_id,
     <prefix>payment_method,
     <prefix>card_type,
     <prefix>card_country,
@@ -43,6 +44,12 @@ getPaymentAttemptForPaymentId() ::= <<
      WHERE payment_id = :payment_id
 >>
 
+getPaymentAttemptsForInvoiceIds(invoiceIds) ::= <<
+    SELECT <paymentAttemptFields()>
+      FROM payment_attempts
+     WHERE invoice_id in (<invoiceIds>)
+>>
+
 getPaymentAttemptForInvoiceId() ::= <<
     SELECT <paymentAttemptFields()>
       FROM payment_attempts
@@ -58,7 +65,7 @@ updatePaymentAttemptWithPaymentId() ::= <<
 
 insertPaymentInfo() ::= <<
     INSERT INTO payments (<paymentInfoFields()>)
-    VALUES (:payment_id, :amount, :refund_amount, :bank_identification_number, :payment_number, :payment_type, :status, :reference_id, :payment_method, :card_type, :card_country, :effective_dt, :created_dt, :updated_dt);
+    VALUES (:payment_id, :amount, :refund_amount, :bank_identification_number, :payment_number, :payment_type, :status, :reference_id, :payment_method_id, :payment_method, :card_type, :card_country, :effective_dt, :created_dt, :updated_dt);
 >>
 
 updatePaymentInfo() ::= <<
@@ -68,4 +75,11 @@ updatePaymentInfo() ::= <<
            card_country = :card_country,
            updated_dt = NOW()
      WHERE payment_id = :payment_id
+>>
+
+getPaymentInfos(invoiceIds) ::= <<
+    SELECT <paymentInfoFields("p.")>
+      FROM payments p, payment_attempts pa
+     WHERE pa.invoice_id in (<invoiceIds>)
+       AND pa.payment_id = p.payment_id
 >>
\ No newline at end of file
diff --git a/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java b/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
index 896ffcc..786ef34 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
@@ -41,12 +41,12 @@ 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.payment.TestHelper;
-import com.ning.billing.util.eventbus.EventBus;
-import com.ning.billing.util.eventbus.EventBus.EventBusException;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
 
 public abstract class TestPaymentApi {
     @Inject
-    private EventBus eventBus;
+    private Bus eventBus;
     @Inject
     protected PaymentApi paymentApi;
     @Inject
@@ -70,14 +70,15 @@ public abstract class TestPaymentApi {
         final BigDecimal amount = new BigDecimal("10.00");
         final UUID subscriptionId = UUID.randomUUID();
 
-        invoice.add(new DefaultInvoiceItem(invoice.getId(),
-                                           subscriptionId,
-                                           now,
-                                           now.plusMonths(1),
-                                           "Test",
-                                           amount,
-                                           new BigDecimal("1.0"),
-                                           Currency.USD));
+        invoice.addInvoiceItem(new DefaultInvoiceItem(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()));
 
@@ -100,6 +101,16 @@ public abstract class TestPaymentApi {
         assertEquals(paymentAttempt.getPaymentId(), paymentInfo.getPaymentId());
         assertEquals(paymentAttempt.getPaymentAttemptDate().withMillisOfSecond(0).withSecondOfMinute(0), now.withMillisOfSecond(0).withSecondOfMinute(0));
 
+        List<PaymentInfo> paymentInfos = paymentApi.getPaymentInfo(Arrays.asList(invoice.getId().toString()));
+        assertNotNull(paymentInfos);
+        assertTrue(paymentInfos.size() > 0);
+
+        PaymentInfo paymentInfoFromGet = paymentInfos.get(0);
+        assertEquals(paymentInfo, paymentInfoFromGet);
+
+        PaymentAttempt paymentAttemptFromGet = paymentApi.getPaymentAttemptForInvoiceId(invoice.getId().toString());
+        assertEquals(paymentAttempt, paymentAttemptFromGet);
+
     }
 
     private PaymentProviderAccount setupAccountWithPaymentMethod() throws AccountApiException {
diff --git a/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java b/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java
index 35ebceb..071ce94 100644
--- a/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java
+++ b/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java
@@ -16,10 +16,14 @@
 
 package com.ning.billing.payment.dao;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.payment.api.PaymentAttempt;
 import com.ning.billing.payment.api.PaymentInfo;
@@ -46,6 +50,12 @@ public class MockPaymentDao implements PaymentDao {
     }
 
     @Override
+    public PaymentAttempt createPaymentAttempt(PaymentAttempt paymentAttempt) {
+        paymentAttempts.put(paymentAttempt.getPaymentAttemptId(), paymentAttempt);
+        return paymentAttempt;
+    }
+
+    @Override
     public void savePaymentInfo(PaymentInfo paymentInfo) {
         payments.put(paymentInfo.getPaymentId(), paymentInfo);
     }
@@ -63,7 +73,7 @@ public class MockPaymentDao implements PaymentDao {
     @Override
     public PaymentAttempt getPaymentAttemptForInvoiceId(String invoiceId) {
         for (PaymentAttempt paymentAttempt : paymentAttempts.values()) {
-            if (invoiceId.equals(paymentAttempt.getInvoiceId())) {
+            if (invoiceId.equals(paymentAttempt.getInvoiceId().toString())) {
                 return paymentAttempt;
             }
         }
@@ -77,15 +87,31 @@ public class MockPaymentDao implements PaymentDao {
     }
 
     @Override
-    public PaymentAttempt getPaymentAttemptById(UUID paymentAttemptId) {
-        // TODO Auto-generated method stub
-        return null;
+    public List<PaymentInfo> getPaymentInfo(List<String> invoiceIds) {
+        List<PaymentAttempt> attempts = getPaymentAttemptsForInvoiceIds(invoiceIds);
+        List<PaymentInfo> paymentsToReturn = new ArrayList<PaymentInfo>(invoiceIds.size());
+
+        for (final PaymentAttempt attempt : attempts) {
+            paymentsToReturn.addAll(Collections2.filter(payments.values(), new Predicate<PaymentInfo>() {
+                @Override
+                public boolean apply(PaymentInfo input) {
+                    return input.getPaymentId().equals(attempt.getPaymentId());
+                }
+            }));
+        }
+        return paymentsToReturn;
     }
 
     @Override
-    public void updatePaymentAttempt(PaymentAttempt updatedPaymentAttempt) {
-        // TODO Auto-generated method stub
-
+    public List<PaymentAttempt> getPaymentAttemptsForInvoiceIds(List<String> invoiceIds) {
+        List<PaymentAttempt> paymentAttempts = new ArrayList<PaymentAttempt>(invoiceIds.size());
+        for (String invoiceId : invoiceIds) {
+            PaymentAttempt attempt = getPaymentAttemptForInvoiceId(invoiceId);
+            if (attempt != null) {
+                paymentAttempts.add(attempt);
+            }
+        }
+        return paymentAttempts;
     }
 
 }
diff --git a/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDao.java b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDao.java
index 6c57c77..7d65b8b 100644
--- a/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDao.java
+++ b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDao.java
@@ -17,17 +17,22 @@
 package com.ning.billing.payment.dao;
 
 import java.math.BigDecimal;
+import java.util.Arrays;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
+import org.testng.Assert;
 import org.testng.annotations.Test;
 
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.payment.api.PaymentAttempt;
 import com.ning.billing.payment.api.PaymentInfo;
 
 public abstract class TestPaymentDao {
 
-    protected PaymentDao dao;
+    protected PaymentDao paymentDao;
 
     @Test
     public void testCreatePayment() {
@@ -44,7 +49,7 @@ public abstract class TestPaymentDao {
                                                            .setEffectiveDate(new DateTime(DateTimeZone.UTC))
                                                            .build();
 
-        dao.savePaymentInfo(paymentInfo);
+        paymentDao.savePaymentInfo(paymentInfo);
     }
 
     @Test
@@ -62,10 +67,51 @@ public abstract class TestPaymentDao {
                                                            .setEffectiveDate(new DateTime(DateTimeZone.UTC))
                                                            .build();
 
-        dao.savePaymentInfo(paymentInfo);
+        paymentDao.savePaymentInfo(paymentInfo);
 
-        dao.updatePaymentInfo("CreditCard", paymentInfo.getPaymentId(), "Visa", "US");
+        paymentDao.updatePaymentInfo("CreditCard", paymentInfo.getPaymentId(), "Visa", "US");
 
     }
 
+    @Test
+    public void testGetPaymentForInvoice() throws AccountApiException {
+        final UUID invoiceId = UUID.randomUUID();
+        final UUID paymentAttemptId = UUID.randomUUID();
+        final UUID accountId = UUID.randomUUID();
+        final String paymentId = UUID.randomUUID().toString();
+        final BigDecimal invoiceAmount = BigDecimal.TEN;
+
+        final DateTime now = new DateTime(DateTimeZone.UTC);
+
+        PaymentAttempt originalPaymenAttempt = new PaymentAttempt(paymentAttemptId, invoiceId, accountId, invoiceAmount, Currency.USD, now, now, paymentId, null, null);
+
+        PaymentAttempt attempt = paymentDao.createPaymentAttempt(originalPaymenAttempt);
+
+        PaymentAttempt attempt2 = paymentDao.getPaymentAttemptForInvoiceId(invoiceId.toString());
+
+        Assert.assertEquals(attempt, attempt2);
+
+        PaymentAttempt attempt3 = paymentDao.getPaymentAttemptsForInvoiceIds(Arrays.asList(invoiceId.toString())).get(0);
+
+        Assert.assertEquals(attempt, attempt3);
+
+        PaymentInfo originalPaymentInfo = new PaymentInfo.Builder().setPaymentId(paymentId)
+                                                           .setAmount(invoiceAmount)
+                                                           .setStatus("Processed")
+                                                           .setBankIdentificationNumber("1234")
+                                                           .setPaymentNumber("12345")
+                                                           .setPaymentMethodId("12345")
+                                                           .setReferenceId("12345")
+                                                           .setType("Electronic")
+                                                           .setCreatedDate(now)
+                                                           .setUpdatedDate(now)
+                                                           .setEffectiveDate(now)
+                                                           .build();
+
+        paymentDao.savePaymentInfo(originalPaymentInfo);
+        PaymentInfo paymentInfo = paymentDao.getPaymentInfo(Arrays.asList(invoiceId.toString())).get(0);
+
+        Assert.assertEquals(originalPaymentInfo, paymentInfo);
+    }
+
 }
diff --git a/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithEmbeddedDb.java b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithEmbeddedDb.java
index da48c03..19ca39d 100644
--- a/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithEmbeddedDb.java
+++ b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithEmbeddedDb.java
@@ -26,28 +26,25 @@ import org.testng.annotations.Test;
 
 import com.ning.billing.dbi.MysqlTestingHelper;
 
-public class TestPaymentDaoWithEmbeddedDb
-{
-    @Test(enabled = true, groups = { "slow", "database" })
-    public class TestPaymentDaoWithEmbeddedDB extends TestPaymentDao {
-        private final MysqlTestingHelper helper = new MysqlTestingHelper();
-
-        @BeforeClass(alwaysRun = true)
-        public void startMysql() throws IOException {
-            final String paymentddl = IOUtils.toString(MysqlTestingHelper.class.getResourceAsStream("/com/ning/billing/payment/ddl.sql"));
-
-            helper.startMysql();
-            helper.initDb(paymentddl);
-        }
-
-        @AfterClass(alwaysRun = true)
-        public void stopMysql() {
-            helper.stopMysql();
-        }
-
-        @BeforeMethod(alwaysRun = true)
-        public void setUp() throws IOException {
-            dao = new DefaultPaymentDao(helper.getDBI());
-        }
+@Test(enabled = true, groups = { "slow", "database" })
+public class TestPaymentDaoWithEmbeddedDb extends TestPaymentDao {
+    private final MysqlTestingHelper helper = new MysqlTestingHelper();
+
+    @BeforeClass(alwaysRun = true)
+    public void startMysql() throws IOException {
+        final String paymentddl = IOUtils.toString(MysqlTestingHelper.class.getResourceAsStream("/com/ning/billing/payment/ddl.sql"));
+
+        helper.startMysql();
+        helper.initDb(paymentddl);
+    }
+
+    @AfterClass(alwaysRun = true)
+    public void stopMysql() {
+        helper.stopMysql();
+    }
+
+    @BeforeMethod(alwaysRun = true)
+    public void setUp() throws IOException {
+        paymentDao = new DefaultPaymentDao(helper.getDBI());
     }
 }
diff --git a/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithMock.java b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithMock.java
index f5af240..6e31f90 100644
--- a/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithMock.java
+++ b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithMock.java
@@ -25,6 +25,6 @@ import org.testng.annotations.Test;
 public class TestPaymentDaoWithMock extends TestPaymentDao {
     @BeforeMethod(alwaysRun = true)
     public void setUp() throws IOException {
-        dao = new MockPaymentDao();
+        paymentDao = new MockPaymentDao();
     }
 }
\ No newline at end of file
diff --git a/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java
index 97e6136..375bcfd 100644
--- a/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java
+++ b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java
@@ -46,7 +46,7 @@ public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
     @Override
     public Either<PaymentError, PaymentInfo> processInvoice(Account account, Invoice invoice) {
         PaymentInfo payment = new PaymentInfo.Builder().setPaymentId(UUID.randomUUID().toString())
-                                             .setAmount(invoice.getAmountOutstanding())
+                                             .setAmount(invoice.getBalance())
                                              .setStatus("Processed")
                                              .setBankIdentificationNumber("1234")
                                              .setCreatedDate(new DateTime())
@@ -55,7 +55,6 @@ public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
                                              .setReferenceId("12345")
                                              .setType("Electronic")
                                              .build();
-
         payments.put(payment.getPaymentId(), payment);
         return Either.right(payment);
     }
diff --git a/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithEmbeddedDb.java b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithEmbeddedDb.java
index 39a080c..9d9372c 100644
--- a/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithEmbeddedDb.java
+++ b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithEmbeddedDb.java
@@ -16,12 +16,12 @@
 
 package com.ning.billing.payment.setup;
 
+import com.ning.billing.util.bus.Bus;
 import org.apache.commons.collections.MapUtils;
 
 import com.google.common.collect.ImmutableMap;
 import com.ning.billing.payment.provider.MockPaymentProviderPluginModule;
-import com.ning.billing.util.eventbus.EventBus;
-import com.ning.billing.util.eventbus.MemoryEventBus;
+import com.ning.billing.util.bus.InMemoryBus;
 
 public class PaymentTestModuleWithEmbeddedDb extends PaymentModule {
     public PaymentTestModuleWithEmbeddedDb() {
@@ -36,6 +36,6 @@ public class PaymentTestModuleWithEmbeddedDb extends PaymentModule {
     @Override
     protected void configure() {
         super.configure();
-        bind(EventBus.class).to(MemoryEventBus.class).asEagerSingleton();
+        bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
     }
 }
diff --git a/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java
index ce7f502..144afa4 100644
--- a/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java
+++ b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java
@@ -16,6 +16,7 @@
 
 package com.ning.billing.payment.setup;
 
+import com.ning.billing.util.bus.InMemoryBus;
 import org.apache.commons.collections.MapUtils;
 
 import com.google.common.collect.ImmutableMap;
@@ -23,11 +24,11 @@ import com.ning.billing.account.dao.AccountDao;
 import com.ning.billing.account.dao.MockAccountDao;
 import com.ning.billing.invoice.dao.InvoiceDao;
 import com.ning.billing.invoice.dao.MockInvoiceDao;
+
 import com.ning.billing.payment.dao.MockPaymentDao;
 import com.ning.billing.payment.dao.PaymentDao;
 import com.ning.billing.payment.provider.MockPaymentProviderPluginModule;
-import com.ning.billing.util.eventbus.EventBus;
-import com.ning.billing.util.eventbus.MemoryEventBus;
+import com.ning.billing.util.bus.Bus;
 
 public class PaymentTestModuleWithMocks extends PaymentModule {
     public PaymentTestModuleWithMocks() {
@@ -47,7 +48,7 @@ public class PaymentTestModuleWithMocks extends PaymentModule {
     @Override
     protected void configure() {
         super.configure();
-        bind(EventBus.class).to(MemoryEventBus.class).asEagerSingleton();
+        bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
         bind(MockAccountDao.class).asEagerSingleton();
         bind(AccountDao.class).to(MockAccountDao.class);
         bind(MockInvoiceDao.class).asEagerSingleton();
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 0d9789c..fd7f5c1 100644
--- a/payment/src/test/java/com/ning/billing/payment/TestHelper.java
+++ b/payment/src/test/java/com/ning/billing/payment/TestHelper.java
@@ -80,16 +80,18 @@ public class TestHelper {
                                      DateTime targetDate,
                                      Currency currency,
                                      InvoiceItem... items) {
-        Invoice invoice = new DefaultInvoice(UUID.randomUUID(), account.getId(), new DateTime(), targetDate, currency, null, new BigDecimal("0"));
+        Invoice invoice = new DefaultInvoice(UUID.randomUUID(), account.getId(), new DateTime(), targetDate, currency);
 
         for (InvoiceItem item : items) {
-            invoice.add(new DefaultInvoiceItem(invoice.getId(),
+            invoice.addInvoiceItem(new DefaultInvoiceItem(invoice.getId(),
                                                item.getSubscriptionId(),
+                                               item.getPlanName(),
+                                               item.getPhaseName(),
                                                item.getStartDate(),
                                                item.getEndDate(),
-                                               item.getDescription(),
-                                               item.getAmount(),
-                                               item.getRate(),
+                                               item.getRecurringAmount(),
+                                               item.getRecurringRate(),
+                                               item.getFixedAmount(),
                                                item.getCurrency()));
         }
         invoiceDao.create(invoice);
@@ -100,7 +102,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, now, now.plusMonths(1), "Test", amount, new BigDecimal("1.0"), Currency.USD);
+        final InvoiceItem item = new DefaultInvoiceItem(null, subscriptionId, "test plan", "test phase", now, now.plusMonths(1), amount, new BigDecimal("1.0"), null, Currency.USD);
 
         return createTestInvoice(account, now, Currency.USD, item);
     }
diff --git a/payment/src/test/java/com/ning/billing/payment/TestNotifyInvoicePaymentApi.java b/payment/src/test/java/com/ning/billing/payment/TestNotifyInvoicePaymentApi.java
index c486c2b..80de67f 100644
--- a/payment/src/test/java/com/ning/billing/payment/TestNotifyInvoicePaymentApi.java
+++ b/payment/src/test/java/com/ning/billing/payment/TestNotifyInvoicePaymentApi.java
@@ -20,6 +20,7 @@ import static org.testng.Assert.assertNotNull;
 
 import java.util.UUID;
 
+import com.ning.billing.invoice.api.InvoicePayment;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Guice;
@@ -32,16 +33,15 @@ import com.ning.billing.account.glue.AccountModuleWithMocks;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoicePaymentApi;
 import com.ning.billing.invoice.glue.InvoiceModuleWithMocks;
-import com.ning.billing.payment.api.InvoicePayment;
 import com.ning.billing.payment.setup.PaymentTestModuleWithMocks;
-import com.ning.billing.util.eventbus.EventBus;
-import com.ning.billing.util.eventbus.EventBus.EventBusException;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
 
 @Test
 @Guice(modules = { PaymentTestModuleWithMocks.class, AccountModuleWithMocks.class, InvoiceModuleWithMocks.class })
 public class TestNotifyInvoicePaymentApi {
     @Inject
-    private EventBus eventBus;
+    private Bus eventBus;
     @Inject
     private RequestProcessor invoiceProcessor;
     @Inject
@@ -69,7 +69,7 @@ public class TestNotifyInvoicePaymentApi {
         PaymentAttempt paymentAttempt = new PaymentAttempt(UUID.randomUUID(), invoice);
 
         invoicePaymentApi.notifyOfPaymentAttempt(invoice.getId(),
-                                     invoice.getAmountOutstanding(),
+                                     invoice.getBalance(),
                                      invoice.getCurrency(),
                                      paymentAttempt.getPaymentAttemptId(),
                                      paymentAttempt.getPaymentAttemptDate());
diff --git a/payment/src/test/java/com/ning/billing/payment/TestPaymentInvoiceIntegration.java b/payment/src/test/java/com/ning/billing/payment/TestPaymentInvoiceIntegration.java
index 4f83f50..8768117 100644
--- a/payment/src/test/java/com/ning/billing/payment/TestPaymentInvoiceIntegration.java
+++ b/payment/src/test/java/com/ning/billing/payment/TestPaymentInvoiceIntegration.java
@@ -26,7 +26,9 @@ import java.math.BigDecimal;
 import java.util.List;
 import java.util.concurrent.Callable;
 
+import com.ning.billing.invoice.glue.InvoiceModuleWithMocks;
 import org.apache.commons.io.IOUtils;
+import org.joda.time.DateTime;
 import org.skife.jdbi.v2.IDBI;
 import org.testng.Assert;
 import org.testng.annotations.AfterClass;
@@ -44,21 +46,21 @@ import com.ning.billing.account.glue.AccountModule;
 import com.ning.billing.dbi.MysqlTestingHelper;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoicePaymentApi;
-import com.ning.billing.invoice.glue.InvoiceModule;
 import com.ning.billing.payment.api.PaymentApi;
 import com.ning.billing.payment.api.PaymentAttempt;
 import com.ning.billing.payment.api.PaymentError;
 import com.ning.billing.payment.api.PaymentInfo;
 import com.ning.billing.payment.setup.PaymentTestModuleWithEmbeddedDb;
-import com.ning.billing.util.eventbus.EventBus;
-import com.ning.billing.util.eventbus.EventBus.EventBusException;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
+import com.ning.billing.util.clock.MockClockModule;
 
 public class TestPaymentInvoiceIntegration {
     // create payment for received invoice and save it -- positive and negative
     // check that notification for payment attempt is created
     // check that invoice-payment is saved
     @Inject
-    private EventBus eventBus;
+    private Bus eventBus;
     @Inject
     private RequestProcessor invoiceProcessor;
     @Inject
@@ -95,7 +97,8 @@ public class TestPaymentInvoiceIntegration {
     public void setUp() throws EventBusException {
         Injector injector = Guice.createInjector(new PaymentTestModuleWithEmbeddedDb(),
                                                  new AccountModule(),
-                                                 new InvoiceModule(),
+                                                 new InvoiceModuleWithMocks(),
+                                                 new MockClockModule(),
                                                  new AbstractModule() {
                                                     @Override
                                                     protected void configure() {
@@ -145,8 +148,12 @@ public class TestPaymentInvoiceIntegration {
         Assert.assertNotNull(invoiceForPayment);
         Assert.assertEquals(invoiceForPayment.getId(), invoice.getId());
         Assert.assertEquals(invoiceForPayment.getAccountId(), account.getId());
-        Assert.assertTrue(invoiceForPayment.getLastPaymentAttempt().isEqual(paymentAttempt.getPaymentAttemptDate()));
-        Assert.assertEquals(invoiceForPayment.getAmountOutstanding().floatValue(), new BigDecimal("0").floatValue());
-        Assert.assertEquals(invoiceForPayment.getAmountPaid().floatValue(), invoice.getAmountOutstanding().floatValue());
+
+        DateTime invoicePaymentAttempt = invoiceForPayment.getLastPaymentAttempt();
+        DateTime correctedDate = invoicePaymentAttempt.minus(invoicePaymentAttempt.millisOfSecond().get());
+        Assert.assertTrue(correctedDate.isEqual(paymentAttempt.getPaymentAttemptDate()));
+
+        Assert.assertEquals(invoiceForPayment.getBalance().floatValue(), new BigDecimal("0").floatValue());
+        Assert.assertEquals(invoiceForPayment.getAmountPaid().floatValue(), invoice.getAmountPaid().floatValue());
     }
 }
diff --git a/payment/src/test/java/com/ning/billing/payment/TestPaymentProvider.java b/payment/src/test/java/com/ning/billing/payment/TestPaymentProvider.java
index 3395421..99870b7 100644
--- a/payment/src/test/java/com/ning/billing/payment/TestPaymentProvider.java
+++ b/payment/src/test/java/com/ning/billing/payment/TestPaymentProvider.java
@@ -18,7 +18,6 @@ package com.ning.billing.payment;
 
 import static com.jayway.awaitility.Awaitility.await;
 import static java.util.concurrent.TimeUnit.MINUTES;
-import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertTrue;
 
@@ -37,13 +36,13 @@ import com.ning.billing.invoice.glue.InvoiceModuleWithMocks;
 import com.ning.billing.payment.api.PaymentError;
 import com.ning.billing.payment.api.PaymentInfo;
 import com.ning.billing.payment.setup.PaymentTestModuleWithMocks;
-import com.ning.billing.util.eventbus.EventBus;
-import com.ning.billing.util.eventbus.EventBus.EventBusException;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
 
 @Guice(modules = { PaymentTestModuleWithMocks.class, AccountModuleWithMocks.class, InvoiceModuleWithMocks.class })
 public class TestPaymentProvider {
     @Inject
-    private EventBus eventBus;
+    private Bus eventBus;
     @Inject
     private RequestProcessor invoiceProcessor;
     @Inject
@@ -58,6 +57,8 @@ public class TestPaymentProvider {
         eventBus.start();
         eventBus.register(invoiceProcessor);
         eventBus.register(paymentInfoReceiver);
+
+        assertTrue(true);
     }
 
     @AfterMethod(alwaysRun = true)
@@ -65,6 +66,8 @@ public class TestPaymentProvider {
         eventBus.unregister(invoiceProcessor);
         eventBus.unregister(paymentInfoReceiver);
         eventBus.stop();
+
+        assertTrue(true);
     }
 
     @Test
@@ -86,23 +89,5 @@ public class TestPaymentProvider {
         assertFalse(paymentInfoReceiver.getProcessedPayments().isEmpty());
         assertTrue(paymentInfoReceiver.getErrors().isEmpty());
 
-        final PaymentInfo paymentInfo = paymentInfoReceiver.getProcessedPayments().get(0);
-        final PaymentInfoRequest paymentInfoRequest = new PaymentInfoRequest(account.getId(), paymentInfo.getPaymentId());
-
-        paymentInfoReceiver.clear();
-        eventBus.post(paymentInfoRequest);
-        await().atMost(5, MINUTES).until(new Callable<Boolean>() {
-            @Override
-            public Boolean call() throws Exception {
-                List<PaymentInfo> processedPayments = paymentInfoReceiver.getProcessedPayments();
-                List<PaymentError> errors = paymentInfoReceiver.getErrors();
-
-                return processedPayments.size() == 1 || errors.size() == 1;
-            }
-        });
-
-        assertFalse(paymentInfoReceiver.getProcessedPayments().isEmpty());
-        assertTrue(paymentInfoReceiver.getErrors().isEmpty());
-        assertEquals(paymentInfoReceiver.getProcessedPayments().get(0), paymentInfo);
     }
 }
diff --git a/payment/src/test/java/com/ning/billing/payment/util/TestSyncWaitOnEventBus.java b/payment/src/test/java/com/ning/billing/payment/util/TestSyncWaitOnEventBus.java
index 3e446cf..0e76771 100644
--- a/payment/src/test/java/com/ning/billing/payment/util/TestSyncWaitOnEventBus.java
+++ b/payment/src/test/java/com/ning/billing/payment/util/TestSyncWaitOnEventBus.java
@@ -27,8 +27,8 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import com.google.common.eventbus.Subscribe;
-import com.ning.billing.util.eventbus.EventBus;
-import com.ning.billing.util.eventbus.MemoryEventBus;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.InMemoryBus;
 
 @Test
 public class TestSyncWaitOnEventBus {
@@ -70,11 +70,11 @@ public class TestSyncWaitOnEventBus {
         }
     }
 
-    private EventBus eventBus;
+    private Bus eventBus;
 
     @BeforeMethod(alwaysRun = true)
     public void setUp() throws Exception {
-        eventBus = new MemoryEventBus();
+        eventBus = new InMemoryBus();
         eventBus.start();
         eventBus.register(new Object() {
             @Subscribe

pom.xml 6(+3 -3)

diff --git a/pom.xml b/pom.xml
index eb22569..b6f6e78 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,7 +17,7 @@
     <groupId>com.ning.billing</groupId>
     <artifactId>killbill</artifactId>
     <packaging>pom</packaging>
-    <version>0.1.3-SNAPSHOT</version>
+    <version>0.1.5-SNAPSHOT</version>
     <name>killbill</name>
     <description>Library for managing recurring subscriptions and the associated billing</description>
     <url>http://github.com/ning/killbill</url>
@@ -232,7 +232,7 @@
             <dependency>
                 <groupId>org.jdbi</groupId>
                 <artifactId>jdbi</artifactId>
-                <version>2.27</version>
+                <version>2.31.2</version>
             </dependency>
             <dependency>
                 <groupId>org.skife.config</groupId>
@@ -425,7 +425,7 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-plugin</artifactId>
-                <version>2.6</version>
+                <version>2.11</version>
                 <configuration>
                     <useManifestOnlyJar>false</useManifestOnlyJar>
                     <systemPropertyVariables>

util/pom.xml 7(+6 -1)

diff --git a/util/pom.xml b/util/pom.xml
index 2b23ebb..b16ecfa 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.3-SNAPSHOT</version>
+        <version>0.1.5-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-util</artifactId>
@@ -97,6 +97,11 @@
             <artifactId>management-dbfiles</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>com.jayway.awaitility</groupId>
+            <artifactId>awaitility</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
     <build>
         <plugins>
diff --git a/util/src/main/java/com/ning/billing/util/customfield/dao/FieldStoreDao.java b/util/src/main/java/com/ning/billing/util/customfield/dao/FieldStoreDao.java
index 680a826..14c123a 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/dao/FieldStoreDao.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/dao/FieldStoreDao.java
@@ -33,6 +33,7 @@ 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.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
 import org.skife.jdbi.v2.tweak.ResultSetMapper;
@@ -42,10 +43,11 @@ import com.ning.billing.util.entity.EntityCollectionDao;
 
 @ExternalizedSqlViaStringTemplate3
 @RegisterMapper(FieldStoreDao.CustomFieldMapper.class)
-public interface FieldStoreDao extends EntityCollectionDao<CustomField>, Transmogrifier {
+public interface FieldStoreDao extends EntityCollectionDao<CustomField>, Transactional<FieldStoreDao>, Transmogrifier {
+
     @Override
-    @SqlBatch
-    public void save(@Bind("objectId") final String objectId,
+    @SqlBatch(transactional=false)
+    public void batchSaveFromTransaction(@Bind("objectId") final String objectId,
                      @Bind("objectType") final String objectType,
                      @CustomFieldBinder final List<CustomField> entities);
 
@@ -65,8 +67,10 @@ public interface FieldStoreDao extends EntityCollectionDao<CustomField>, Transmo
     @Target({ElementType.PARAMETER})
     public @interface CustomFieldBinder {
         public static class CustomFieldBinderFactory implements BinderFactory {
+            @Override
             public Binder build(Annotation annotation) {
                 return new Binder<CustomFieldBinder, CustomField>() {
+                    @Override
                     public void bind(SQLStatement q, CustomFieldBinder bind, CustomField customField) {
                         q.bind("id", customField.getId().toString());
                         q.bind("fieldName", customField.getName());
diff --git a/util/src/main/java/com/ning/billing/util/entity/EntityCollectionDao.java b/util/src/main/java/com/ning/billing/util/entity/EntityCollectionDao.java
index 8134203..11d149c 100644
--- a/util/src/main/java/com/ning/billing/util/entity/EntityCollectionDao.java
+++ b/util/src/main/java/com/ning/billing/util/entity/EntityCollectionDao.java
@@ -26,8 +26,9 @@ import java.util.List;
  * @param <T>
  */
 public interface EntityCollectionDao<T extends Entity> {
-    @SqlBatch
-    public void save(@Bind("objectId") final String objectId,
+
+    @SqlBatch(transactional=false)
+    public void batchSaveFromTransaction(@Bind("objectId") final String objectId,
                      @Bind("objectType") final String objectType,
                      @BindBean final List<T> entities);
 
diff --git a/util/src/main/java/com/ning/billing/util/globalLocker/GlobalLock.java b/util/src/main/java/com/ning/billing/util/globalLocker/GlobalLock.java
new file mode 100644
index 0000000..62a61a5
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/globalLocker/GlobalLock.java
@@ -0,0 +1,22 @@
+/*
+ * 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.util.globalLocker;
+
+public interface GlobalLock
+{
+    public void release();
+}
\ No newline at end of file
diff --git a/util/src/main/java/com/ning/billing/util/globalLocker/LockFailedException.java b/util/src/main/java/com/ning/billing/util/globalLocker/LockFailedException.java
new file mode 100644
index 0000000..341edc3
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/globalLocker/LockFailedException.java
@@ -0,0 +1,21 @@
+/*
+ * 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.util.globalLocker;
+
+public class LockFailedException extends RuntimeException
+{
+}
diff --git a/util/src/main/java/com/ning/billing/util/globalLocker/MySqlGlobalLocker.java b/util/src/main/java/com/ning/billing/util/globalLocker/MySqlGlobalLocker.java
new file mode 100644
index 0000000..762ddc7
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/globalLocker/MySqlGlobalLocker.java
@@ -0,0 +1,106 @@
+/*
+ * 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.util.globalLocker;
+
+import com.google.inject.Inject;
+import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.IDBI;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class MySqlGlobalLocker implements GlobalLocker {
+
+    private final static Logger logger = LoggerFactory.getLogger(MySqlGlobalLocker.class);
+
+    private final static long DEFAULT_TIMEOUT = 3L; // 3 seconds
+
+    private final IDBI dbi;
+    private long timeout;
+
+    @Inject
+    public MySqlGlobalLocker(IDBI dbi) {
+        this.dbi = dbi;
+        this.timeout = DEFAULT_TIMEOUT;
+    }
+
+    public void setTimeout(final long timeout) {
+        this.timeout = timeout;
+    }
+
+    @Override
+    public GlobalLock lockWithNumberOfTries(final LockerService service, final String lockKey, final int retry) {
+
+        final String lockName = getLockName(service, lockKey);
+        int tries_left = retry;
+        while (tries_left-- > 0) {
+            GlobalLock lock = lock(lockName);
+            if (lock != null) {
+                return lock;
+            }
+        }
+        logger.error(String.format("Failed to acquire lock %s for service %s after %d retry", lockKey, service, retry));
+        throw new LockFailedException();
+    }
+
+    private GlobalLock lock(final String lockName) throws LockFailedException {
+
+        final Handle h = dbi.open();
+        final MySqlGlobalLockerDao dao = h.attach(MySqlGlobalLockerDao.class);
+
+        final boolean obtained = dao.lock(lockName, timeout);
+        if (obtained) {
+            return new GlobalLock() {
+                @Override
+                public void release() {
+                    try {
+                        dao.releaseLock(lockName);
+                    }
+                    finally {
+                        if (h != null) {
+                            h.close();
+                        }
+                    }
+                }
+            };
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public Boolean isFree(final LockerService service, final String lockKey) {
+
+        final String lockName = getLockName(service, lockKey);
+        final Handle h = dbi.open();
+        try {
+            final MySqlGlobalLockerDao dao = h.attach(MySqlGlobalLockerDao.class);
+            return dao.isFree(lockName);
+        } finally {
+            if (h != null) {
+                h.close();
+            }
+        }
+    }
+
+    private String getLockName(final LockerService service, final String lockKey) {
+        StringBuilder tmp = new StringBuilder()
+            .append(service.toString())
+            .append("-")
+            .append(lockKey);
+        return tmp.toString();
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/globalLocker/MySqlGlobalLockerDao.java b/util/src/main/java/com/ning/billing/util/globalLocker/MySqlGlobalLockerDao.java
new file mode 100644
index 0000000..14a02d4
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/globalLocker/MySqlGlobalLockerDao.java
@@ -0,0 +1,46 @@
+/*
+ * 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.util.globalLocker;
+
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+@RegisterMapper(MySqlGlobalLockerDao.LockMapper.class)
+public interface MySqlGlobalLockerDao {
+
+    @SqlQuery("Select GET_LOCK(:lockName, :timeout);")
+    public Boolean lock(@Bind("lockName") final String lockName, @Bind("timeout") final long timeout);
+
+    @SqlQuery("Select RELEASE_LOCK(:lockName);")
+    public Boolean releaseLock(@Bind("lockName") final String lockName);
+
+    @SqlQuery("Select IS_FREE_LOCK(:lockName);")
+    public Boolean isFree(@Bind("lockName") final String lockName);
+
+    class LockMapper implements ResultSetMapper<Boolean> {
+         @Override
+         public Boolean map(int index, ResultSet r, StatementContext ctx) throws SQLException {
+            return (r.getByte(1) == 1);
+         }
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/glue/BusModule.java b/util/src/main/java/com/ning/billing/util/glue/BusModule.java
new file mode 100644
index 0000000..d6f7a37
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/glue/BusModule.java
@@ -0,0 +1,34 @@
+/*
+ * 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.util.glue;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.util.bus.DefaultBusService;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.BusService;
+import com.ning.billing.util.bus.InMemoryBus;
+
+public class BusModule extends AbstractModule {
+
+    @Override
+    protected void configure() {
+        bind(BusService.class).to(DefaultBusService.class);
+        bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
+
+    }
+
+}
diff --git a/util/src/main/java/com/ning/billing/util/glue/TagStoreModule.java b/util/src/main/java/com/ning/billing/util/glue/TagStoreModule.java
index 10651be..a598275 100644
--- a/util/src/main/java/com/ning/billing/util/glue/TagStoreModule.java
+++ b/util/src/main/java/com/ning/billing/util/glue/TagStoreModule.java
@@ -18,8 +18,6 @@ package com.ning.billing.util.glue;
 
 import com.google.inject.AbstractModule;
 import com.ning.billing.util.api.TagDefinitionUserApi;
-import com.ning.billing.util.clock.Clock;
-import com.ning.billing.util.clock.DefaultClock;
 import com.ning.billing.util.tag.api.DefaultTagDefinitionUserApi;
 import com.ning.billing.util.tag.dao.DefaultTagDefinitionDao;
 import com.ning.billing.util.tag.dao.TagDefinitionDao;
@@ -28,13 +26,16 @@ import com.ning.billing.util.tag.dao.TagStoreSqlDao;
 
 public class TagStoreModule extends AbstractModule
 {
-    @Override
-    protected void configure()
-    {
+    protected void installDaos() {
         bind(TagDefinitionSqlDao.class).toProvider(TagDescriptionDaoProvider.class).asEagerSingleton();
         bind(TagDefinitionDao.class).to(DefaultTagDefinitionDao.class).asEagerSingleton();
         bind(TagStoreSqlDao.class).toProvider(TagStoreDaoProvider.class).asEagerSingleton();
+    }
+
+    @Override
+    protected void configure()
+    {
+        installDaos();
         bind(TagDefinitionUserApi.class).to(DefaultTagDefinitionUserApi.class).asEagerSingleton();
     }
-    
 }
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/dao/NotificationSqlDao.java b/util/src/main/java/com/ning/billing/util/notificationq/dao/NotificationSqlDao.java
index 818d831..2f511ec 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/dao/NotificationSqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/dao/NotificationSqlDao.java
@@ -49,13 +49,13 @@ public interface NotificationSqlDao extends Transactional<NotificationSqlDao>, C
     //
     @SqlQuery
     @Mapper(NotificationSqlMapper.class)
-    public List<Notification> getReadyNotifications(@Bind("now") Date now, @Bind("max") int max);
+    public List<Notification> getReadyNotifications(@Bind("now") Date now, @Bind("max") int max, @Bind("queue_name") String queueName);
 
     @SqlUpdate
-    public int claimNotification(@Bind("owner") String owner, @Bind("next_available") Date nextAvailable, @Bind("notification_id") String eventId, @Bind("now") Date now);
+    public int claimNotification(@Bind("owner") String owner, @Bind("next_available") Date nextAvailable, @Bind("id") long id, @Bind("now") Date now);
 
     @SqlUpdate
-    public void clearNotification(@Bind("notification_id") String eventId, @Bind("owner") String owner);
+    public void clearNotification(@Bind("id") long id, @Bind("owner") String owner);
 
     @SqlUpdate
     public void insertNotification(@Bind(binder = NotificationSqlDaoBinder.class) Notification evt);
@@ -71,12 +71,13 @@ public interface NotificationSqlDao extends Transactional<NotificationSqlDao>, C
 
         @Override
         public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, Notification evt) {
-            stmt.bind("notification_id", evt.getId().toString());
+            stmt.bind("notification_id", evt.getUUID().toString());
             stmt.bind("created_dt", getDate(new DateTime()));
             stmt.bind("notification_key", evt.getNotificationKey());
             stmt.bind("effective_dt", getDate(evt.getEffectiveDate()));
+            stmt.bind("queue_name", evt.getQueueName());
             stmt.bind("processing_available_dt", getDate(evt.getNextAvailableDate()));
-            stmt.bind("processing_owner", (String) null);
+            stmt.bind("processing_owner", evt.getOwner());
             stmt.bind("processing_state", NotificationLifecycleState.AVAILABLE.toString());
         }
     }
@@ -93,14 +94,16 @@ public interface NotificationSqlDao extends Transactional<NotificationSqlDao>, C
         public Notification map(int index, ResultSet r, StatementContext ctx)
         throws SQLException {
 
-            final UUID id = UUID.fromString(r.getString("notification_id"));
+            final long id = r.getLong("id");
+            final UUID uuid = UUID.fromString(r.getString("notification_id"));
             final String notificationKey = r.getString("notification_key");
+            final String queueName = r.getString("queue_name");
             final DateTime effectiveDate = getDate(r, "effective_dt");
             final DateTime nextAvailableDate = getDate(r, "processing_available_dt");
             final String processingOwner = r.getString("processing_owner");
             final NotificationLifecycleState processingState = NotificationLifecycleState.valueOf(r.getString("processing_state"));
 
-            return new DefaultNotification(id, processingOwner, nextAvailableDate,
+            return new DefaultNotification(id, uuid, processingOwner, queueName, nextAvailableDate,
                     processingState, notificationKey, effectiveDate);
 
         }
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotification.java b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotification.java
index 2946e13..26e6c4e 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotification.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotification.java
@@ -22,33 +22,41 @@ import org.joda.time.DateTime;
 
 public class DefaultNotification implements Notification {
 
-    private final UUID id;
+    private final long id;
+    private final UUID uuid;
     private final String owner;
+    private final String queueName;
     private final DateTime nextAvailableDate;
     private final NotificationLifecycleState lifecycleState;
     private final String notificationKey;
     private final DateTime effectiveDate;
 
 
-    public DefaultNotification(UUID id, String owner, DateTime nextAvailableDate,
+    public DefaultNotification(long id, UUID uuid, String owner, String queueName, DateTime nextAvailableDate,
             NotificationLifecycleState lifecycleState,
             String notificationKey, DateTime effectiveDate) {
         super();
         this.id = id;
+        this.uuid = uuid;
         this.owner = owner;
+        this.queueName = queueName;
         this.nextAvailableDate = nextAvailableDate;
         this.lifecycleState = lifecycleState;
         this.notificationKey = notificationKey;
         this.effectiveDate = effectiveDate;
     }
 
-    public DefaultNotification(String notificationKey, DateTime effectiveDate) {
-        this(UUID.randomUUID(), null, null, NotificationLifecycleState.AVAILABLE, notificationKey, effectiveDate);
+    @Override
+    public long getId() {
+        return id;
     }
 
+    public DefaultNotification(String queueName, String notificationKey, DateTime effectiveDate) {
+        this(-1L, UUID.randomUUID(), null, queueName, null, NotificationLifecycleState.AVAILABLE, notificationKey, effectiveDate);
+    }
     @Override
-    public UUID getId() {
-        return id;
+    public UUID getUUID() {
+        return uuid;
     }
 
     @Override
@@ -94,4 +102,10 @@ public class DefaultNotification implements Notification {
     public DateTime getEffectiveDate() {
         return effectiveDate;
     }
+
+	@Override
+	public String getQueueName() {
+		return queueName;
+	}
+
 }
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 16d28b2..3c19a2b 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
@@ -17,15 +17,10 @@
 package com.ning.billing.util.notificationq;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Date;
 import java.util.List;
-
 import org.joda.time.DateTime;
-import org.skife.jdbi.v2.DBI;
 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 com.ning.billing.util.clock.Clock;
@@ -37,48 +32,45 @@ public class DefaultNotificationQueue extends NotificationQueueBase {
     protected final NotificationSqlDao dao;
 
     public DefaultNotificationQueue(final IDBI dbi, final Clock clock,  final String svcName, final String queueName, final NotificationQueueHandler handler, final NotificationConfig config) {
+
         super(clock, svcName, queueName, handler, config);
         this.dao = dbi.onDemand(NotificationSqlDao.class);
     }
 
     @Override
-    protected void doProcessEvents(int sequenceId) {
+    protected void doProcessEvents(final int sequenceId) {
+
+        logDebug("ENTER doProcessEvents");
         List<Notification> notifications = getReadyNotifications(sequenceId);
-        for (Notification cur : notifications) {
+        if (notifications.size() == 0) {
+            logDebug("EXIT doProcessEvents");
+            return;
+        }
+
+        logDebug("START processing %d events at time %s", notifications.size(), clock.getUTCNow().toDate());
+
+        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());
+            clearNotification(cur);
+            logDebug("done handling notification %s, key = %s for time %s",
+                    cur.getUUID(), cur.getNotificationKey(), cur.getEffectiveDate());
         }
-        // If anything happens before we get to clear those notifications, somebody else will pick them up
-        clearNotifications(notifications);
     }
 
     @Override
     public void recordFutureNotificationFromTransaction(final Transmogrifier transactionalDao,
             final DateTime futureNotificationTime, final NotificationKey notificationKey) {
         NotificationSqlDao transactionalNotificationDao =  transactionalDao.become(NotificationSqlDao.class);
-        Notification notification = new DefaultNotification(notificationKey.toString(), futureNotificationTime);
+        Notification notification = new DefaultNotification(getFullQName(), notificationKey.toString(), futureNotificationTime);
         transactionalNotificationDao.insertNotification(notification);
     }
 
 
-    private void clearNotifications(final Collection<Notification> cleared) {
-
-        log.debug(String.format("NotificationQueue %s clearEventsReady START cleared size = %d",
-                getFullQName(),
-                cleared.size()));
-
-        dao.inTransaction(new Transaction<Void, NotificationSqlDao>() {
-
-            @Override
-            public Void inTransaction(NotificationSqlDao transactional,
-                    TransactionStatus status) throws Exception {
-                for (Notification cur : cleared) {
-                    transactional.clearNotification(cur.getId().toString(), hostname);
-                    log.debug(String.format("NotificationQueue %s cleared events %s", getFullQName(), cur.getId()));
-                }
-                return null;
-            }
-        });
+    private void clearNotification(final Notification cleared) {
+        dao.clearNotification(cleared.getId(), hostname);
     }
 
     private List<Notification> getReadyNotifications(final int seqId) {
@@ -86,35 +78,34 @@ public class DefaultNotificationQueue extends NotificationQueueBase {
         final Date now = clock.getUTCNow().toDate();
         final Date nextAvailable = clock.getUTCNow().plus(config.getDaoClaimTimeMs()).toDate();
 
-        log.debug(String.format("NotificationQueue %s getEventsReady START effectiveNow =  %s",  getFullQName(), now));
-
-        List<Notification> result = dao.inTransaction(new Transaction<List<Notification>, NotificationSqlDao>() {
-
-            @Override
-            public List<Notification> inTransaction(NotificationSqlDao transactionalDao,
-                    TransactionStatus status) throws Exception {
-
-                List<Notification> claimedNotifications = new ArrayList<Notification>();
-                List<Notification> input = transactionalDao.getReadyNotifications(now, config.getDaoMaxReadyEvents());
-                for (Notification cur : input) {
-                    final boolean claimed = (transactionalDao.claimNotification(hostname, nextAvailable, cur.getId().toString(), now) == 1);
-                    if (claimed) {
-                        claimedNotifications.add(cur);
-                        transactionalDao.insertClaimedHistory(seqId, hostname, now, cur.getId().toString());
-                    }
-                }
-                return claimedNotifications;
+        List<Notification> input = dao.getReadyNotifications(now, config.getDaoMaxReadyEvents(), getFullQName());
+
+        List<Notification> claimedNotifications = new ArrayList<Notification>();
+        for (Notification cur : input) {
+            logDebug("about to claim notification %s,  key = %s for time %s",
+                    cur.getUUID(), cur.getNotificationKey(), cur.getEffectiveDate());
+            final boolean claimed = (dao.claimNotification(hostname, nextAvailable, cur.getId(), now) == 1);
+            logDebug("claimed notification %s, key = %s for time %s result = %s",
+                    cur.getUUID(), cur.getNotificationKey(), cur.getEffectiveDate(), Boolean.valueOf(claimed));
+            if (claimed) {
+                claimedNotifications.add(cur);
+                dao.insertClaimedHistory(seqId, hostname, now, cur.getUUID().toString());
             }
-        });
+        }
 
-        for (Notification cur : result) {
-            log.debug(String.format("NotificationQueue %sclaimed events %s",
-                    getFullQName(), cur.getId()));
+        for (Notification cur : claimedNotifications) {
             if (cur.getOwner() != null && !cur.getOwner().equals(hostname)) {
                 log.warn(String.format("NotificationQueue %s stealing notification %s from %s",
                         getFullQName(), cur, cur.getOwner()));
             }
         }
-        return result;
+        return claimedNotifications;
+    }
+
+    private void logDebug(String format, Object...args) {
+        if (log.isDebugEnabled()) {
+            String realDebug = String.format(format, args);
+            log.debug(String.format("Thread %d [queue = %s] %s", Thread.currentThread().getId(), getFullQName(), realDebug));
+        }
     }
 }
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 fe18ead..91e7110 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
@@ -17,7 +17,6 @@
 package com.ning.billing.util.notificationq;
 
 import org.skife.jdbi.v2.IDBI;
-
 import com.google.inject.Inject;
 import com.ning.billing.util.clock.Clock;
 
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/Notification.java b/util/src/main/java/com/ning/billing/util/notificationq/Notification.java
index 651469b..d59098b 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/Notification.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/Notification.java
@@ -23,9 +23,15 @@ import org.joda.time.DateTime;
 
 public interface Notification extends NotificationLifecycle {
 
-    public UUID getId();
+    public long getId();
+
+    public UUID getUUID();
 
     public String getNotificationKey();
 
     public DateTime getEffectiveDate();
+
+    public String getQueueName();
+
+
 }
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/NotificationError.java b/util/src/main/java/com/ning/billing/util/notificationq/NotificationError.java
new file mode 100644
index 0000000..4e771ba
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationError.java
@@ -0,0 +1,38 @@
+/*
+ * 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.util.notificationq;
+
+public class NotificationError extends Error {
+
+    private static final long serialVersionUID = 131398536;
+
+    public NotificationError() {
+        super();
+    }
+
+    public NotificationError(String msg, Throwable arg1) {
+        super(msg, arg1);
+    }
+
+    public NotificationError(String msg) {
+        super(msg);
+    }
+
+    public NotificationError(Throwable msg) {
+        super(msg);
+    }
+}
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 23f0de0..4ea38f7 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
@@ -42,18 +42,17 @@ public interface NotificationQueue {
    public void processReadyNotification();
 
    /**
-    * Stops the queue.
+    * Stops the queue. Blocks until queue is completely stopped.
     *
     * @see NotificationQueueHandler.completedQueueStop to be notified when the notification thread exited
     */
    public void stopQueue();
 
    /**
-    * Starts the queue.
+    * Starts the queue. Blocks until queue has completely started.
     *
     * @see NotificationQueueHandler.completedQueueStart to be notified when the notification thread started
     */
    public void startQueue();
 
-
 }
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 6eaf33f..9a42d2e 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
@@ -17,34 +17,28 @@
 package com.ning.billing.util.notificationq;
 
 import java.lang.Thread.UncaughtExceptionHandler;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Date;
-import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 
-import org.joda.time.DateTime;
-import org.skife.jdbi.v2.DBI;
-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.ning.billing.util.Hostname;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
-import com.ning.billing.util.notificationq.dao.NotificationSqlDao;
 
 
 public abstract class NotificationQueueBase implements NotificationQueue {
 
     protected final static Logger log = LoggerFactory.getLogger(NotificationQueueBase.class);
 
+    private static final long MAX_NOTIFICATION_THREAD_WAIT_MS = 10000; // 10 secs
+    private static final long NOTIFICATION_THREAD_WAIT_INCREMENT_MS = 1000; // 1 sec
+    private static final long NANO_TO_MS = (1000 * 1000);
+
     protected static final String NOTIFICATION_THREAD_PREFIX = "Notification-";
     protected final long STOP_WAIT_TIMEOUT_MS = 60000;
 
@@ -63,7 +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;
@@ -92,14 +89,14 @@ public abstract class NotificationQueueBase implements NotificationQueue {
 
     @Override
     public void processReadyNotification() {
-        // STEPH to be implemented
+        doProcessEvents(sequenceId.incrementAndGet());
     }
 
 
     @Override
     public void stopQueue() {
         if (config.isNotificationProcessingOff()) {
-            handler.completedQueueStop();
+            completedQueueStop();
             return;
         }
 
@@ -113,7 +110,7 @@ public abstract class NotificationQueueBase implements NotificationQueue {
                 log.warn("NotificationQueue got interrupted exception when stopping notifications", e);
             }
         }
-
+        waitForNotificationStopCompletion();
     }
 
     @Override
@@ -125,7 +122,7 @@ public abstract class NotificationQueueBase implements NotificationQueue {
 
         if (config.isNotificationProcessingOff()) {
             log.warn(String.format("KILLBILL NOTIFICATION PROCESSING FOR SVC %s IS OFF !!!", getFullQName()));
-            handler.completedQueueStart();
+            completedQueueStart();
             return;
         }
         final NotificationQueueBase notificationQueue = this;
@@ -139,7 +136,7 @@ public abstract class NotificationQueueBase implements NotificationQueue {
                         Thread.currentThread().getId()));
 
                 // Thread is now started, notify the listener
-                handler.completedQueueStart();
+                completedQueueStart();
 
                 try {
                     while (true) {
@@ -171,7 +168,7 @@ public abstract class NotificationQueueBase implements NotificationQueue {
                     // Just to make it really obvious in the log
                     e.printStackTrace();
                 } finally {
-                    handler.completedQueueStop();
+                    completedQueueStop();
                     log.info(String.format("NotificationQueue thread  %s  [%d] exited...",
                             Thread.currentThread().getName(),
                             Thread.currentThread().getId()));
@@ -182,8 +179,59 @@ public abstract class NotificationQueueBase implements NotificationQueue {
                 Thread.sleep(config.getNotificationSleepTimeMs());
             }
         });
+        waitForNotificationStartCompletion();
+    }
+    
+    private void completedQueueStop() {
+    	synchronized (this) {
+    		stoppedComplete = true;
+            this.notifyAll();
+        }
+    }
+    
+    private void completedQueueStart() {
+        synchronized (this) {
+        	startedComplete = true;
+            this.notifyAll();
+        }
     }
 
+    private void waitForNotificationStartCompletion() {
+        waitForNotificationEventCompletion(true);
+    }
+
+    private void waitForNotificationStopCompletion() {
+        waitForNotificationEventCompletion(false);
+    }
+
+    private void waitForNotificationEventCompletion(boolean startEvent) {
+
+        long ini = System.nanoTime();
+        synchronized(this) {
+            do {
+                if ((startEvent ? startedComplete : stoppedComplete)) {
+                    break;
+                }
+                try {
+                    this.wait(NOTIFICATION_THREAD_WAIT_INCREMENT_MS);
+                } catch (InterruptedException e ) {
+                    Thread.currentThread().interrupt();
+                    throw new NotificationError(e);
+                }
+            } while (!(startEvent ? startedComplete : stoppedComplete) &&
+                    (System.nanoTime() - ini) / NANO_TO_MS < MAX_NOTIFICATION_THREAD_WAIT_MS);
+
+            if (!(startEvent ? startedComplete : stoppedComplete)) {
+                log.error("Could not {} notification thread in {} msec !!!",
+                        (startEvent ? "start" : "stop"),
+                        MAX_NOTIFICATION_THREAD_WAIT_MS);
+                throw new NotificationError("Failed to start service!!");
+            }
+            log.info("Notification thread has been {} in {} ms",
+                    (startEvent ? "started" : "stopped"),
+                    (System.nanoTime() - ini) / NANO_TO_MS);
+        }
+    }
 
     protected String getFullQName() {
         return svcName + ":" +  queueName;
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 a18906b..4fb17b5 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,33 +16,22 @@
 
 package com.ning.billing.util.notificationq;
 
-import java.util.NoSuchElementException;
-
 
 public interface NotificationQueueService {
 
     public interface NotificationQueueHandler {
         /**
-         * Called when the Notification thread has been started
-         */
-        public void completedQueueStart();
-
-        /**
          * Called for each notification ready
          *
-         * @param key the notification key associated to that notification entry
+         * @param notificationKey the notification key associated to that notification entry
          */
         public void handleReadyNotification(String notificationKey);
-        /**
-         * Called right before the Notification thread is about to exit
-         */
-        public void completedQueueStop();
-    }
+     }
 
-    public static final class NotficationQueueAlreadyExists extends Exception {
+    public static final class NotificationQueueAlreadyExists extends Exception {
         private static final long serialVersionUID = 1541281L;
 
-        public NotficationQueueAlreadyExists(String msg) {
+        public NotificationQueueAlreadyExists(String msg) {
             super(msg);
         }
     }
@@ -65,11 +54,11 @@ public interface NotificationQueueService {
      *
      * @return a new NotificationQueue
      *
-     * @throws NotficationQueueAlreadyExists is the queue associated with that service and name already exits
+     * @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)
-        throws NotficationQueueAlreadyExists;
+        throws NotificationQueueAlreadyExists;
 
     /**
      * Retrieves an already created NotificationQueue by service and name if it exists
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 a4dc64e..98a10b1 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
@@ -43,7 +43,7 @@ public abstract class NotificationQueueServiceBase implements NotificationQueueS
     @Override
     public NotificationQueue createNotificationQueue(String svcName,
             String queueName, NotificationQueueHandler handler,
-            NotificationConfig config) throws NotficationQueueAlreadyExists {
+            NotificationConfig config) throws NotificationQueueAlreadyExists {
         if (svcName == null || queueName == null || handler == null || config == null) {
             throw new RuntimeException("Need to specify all parameters");
         }
@@ -53,7 +53,7 @@ public abstract class NotificationQueueServiceBase implements NotificationQueueS
         synchronized(queues) {
             result = queues.get(compositeName);
             if (result != null) {
-                throw new NotficationQueueAlreadyExists(String.format("Queue for svc %s and name %s already exist",
+                throw new NotificationQueueAlreadyExists(String.format("Queue for svc %s and name %s already exist",
                         svcName, queueName));
             }
             result = createNotificationQueueInternal(svcName, queueName, handler, config);
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/TagStoreSqlDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/TagStoreSqlDao.java
index 33dcdb0..bf4ed62 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/TagStoreSqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/TagStoreSqlDao.java
@@ -16,38 +16,23 @@
 
 package com.ning.billing.util.tag.dao;
 
-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.sql.ResultSet;
-import java.sql.SQLException;
+
 import java.util.List;
-import java.util.UUID;
-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.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
 import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
-import org.skife.jdbi.v2.tweak.ResultSetMapper;
 import com.ning.billing.util.entity.EntityCollectionDao;
-import com.ning.billing.util.tag.DescriptiveTag;
-import com.ning.billing.util.tag.DefaultTagDefinition;
 import com.ning.billing.util.tag.Tag;
-import com.ning.billing.util.tag.TagDefinition;
 
 @ExternalizedSqlViaStringTemplate3
 @RegisterMapper(TagMapper.class)
-public interface TagStoreSqlDao extends EntityCollectionDao<Tag> {
+public interface TagStoreSqlDao extends EntityCollectionDao<Tag>, Transactional<TagStoreSqlDao> {
     @Override
-    @SqlBatch
-    public void save(@Bind("objectId") final String objectId,
+    @SqlBatch(transactional=false)
+    public void batchSaveFromTransaction(@Bind("objectId") final String objectId,
                      @Bind("objectType") final String objectType,
                      @TagBinder final List<Tag> entities);
 }
\ No newline at end of file
diff --git a/util/src/main/resources/com/ning/billing/util/customfield/dao/FieldStoreDao.sql.stg b/util/src/main/resources/com/ning/billing/util/customfield/dao/FieldStoreDao.sql.stg
index 883f61b..9d3e96e 100644
--- a/util/src/main/resources/com/ning/billing/util/customfield/dao/FieldStoreDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/customfield/dao/FieldStoreDao.sql.stg
@@ -1,6 +1,6 @@
 group FieldStoreDao;
 
-save() ::= <<
+batchSaveFromTransaction() ::= <<
   INSERT INTO custom_fields(id, object_id, object_type, field_name, field_value)
   VALUES (:id, :objectId, :objectType, :fieldName, :fieldValue)
   ON DUPLICATE KEY UPDATE
diff --git a/util/src/main/resources/com/ning/billing/util/ddl.sql b/util/src/main/resources/com/ning/billing/util/ddl.sql
index a65e0cd..a0ef302 100644
--- a/util/src/main/resources/com/ning/billing/util/ddl.sql
+++ b/util/src/main/resources/com/ning/billing/util/ddl.sql
@@ -10,6 +10,7 @@ CREATE TABLE custom_fields (
 CREATE INDEX custom_fields_object_id_object_type ON custom_fields(object_id, object_type);
 CREATE UNIQUE INDEX custom_fields_unique ON custom_fields(object_id, object_type, field_name);
 
+DROP TABLE IF EXISTS tag_descriptions;
 DROP TABLE IF EXISTS tag_definitions;
 CREATE TABLE tag_definitions (
   id char(36) NOT NULL,
@@ -41,14 +42,14 @@ CREATE TABLE notifications (
     created_dt datetime NOT NULL,
 	notification_key varchar(256) NOT NULL,
     effective_dt datetime NOT NULL,
-    processing_owner char(36) DEFAULT NULL,
+    queue_name char(64) NOT NULL,
+    processing_owner char(50) DEFAULT NULL,
     processing_available_dt datetime DEFAULT NULL,
     processing_state varchar(14) DEFAULT 'AVAILABLE',
     PRIMARY KEY(id)
 ) ENGINE=innodb;
-CREATE INDEX  `idx_comp_where` ON notifications (`effective_dt`,`processing_state`,`processing_owner`,`processing_available_dt`);
-CREATE INDEX  `idx_update` ON notifications (`notification_id`,`processing_state`,`processing_owner`,`processing_available_dt`);
-CREATE INDEX  `idx_update1` ON notifications (`notification_id`,`processing_owner`);
+CREATE INDEX  `idx_comp_where` ON notifications (`effective_dt`, `queue_name`, `processing_state`,`processing_owner`,`processing_available_dt`);
+CREATE INDEX  `idx_update` ON notifications (`processing_state`,`processing_owner`,`processing_available_dt`);
 CREATE INDEX  `idx_get_ready` ON notifications (`effective_dt`,`created_dt`,`id`);
 
 DROP TABLE IF EXISTS claimed_notifications;
diff --git a/util/src/main/resources/com/ning/billing/util/notificationq/dao/NotificationSqlDao.sql.stg b/util/src/main/resources/com/ning/billing/util/notificationq/dao/NotificationSqlDao.sql.stg
index 5a44431..7a7ecab 100644
--- a/util/src/main/resources/com/ning/billing/util/notificationq/dao/NotificationSqlDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/notificationq/dao/NotificationSqlDao.sql.stg
@@ -2,48 +2,49 @@ group NotificationSqlDao;
 
 getReadyNotifications(now, max) ::= <<
     select
-      notification_id
-    , notification_key
+      id
+      ,  notification_id
+      , notification_key
       , created_dt
       , effective_dt
+      , queue_name
       , processing_owner
       , processing_available_dt
       , processing_state
     from notifications
     where
       effective_dt \<= :now
+      and queue_name = :queue_name
       and processing_state != 'PROCESSED'
       and (processing_owner IS NULL OR processing_available_dt \<= :now)
     order by
       effective_dt asc
       , created_dt asc
-      , id asc
+      , id
     limit :max
     ;
 >>
 
 
-claimNotification(owner, next_available, notification_id, now) ::= <<
+claimNotification(owner, next_available, id, now) ::= <<
     update notifications
     set
       processing_owner = :owner
       , processing_available_dt = :next_available
       , processing_state = 'IN_PROCESSING'
     where
-      notification_id = :notification_id
+      id = :id
       and processing_state != 'PROCESSED'
       and (processing_owner IS NULL OR processing_available_dt \<= :now)
     ;
 >>
 
-clearNotification(notification_id, owner) ::= <<
+clearNotification(id, owner) ::= <<
     update notifications
     set
-      processing_owner = NULL
-      , processing_state = 'PROCESSED'
+      processing_state = 'PROCESSED'
     where
-      notification_id = :notification_id
-      and processing_owner = :owner
+      id = :id
     ;
 >>
 
@@ -53,6 +54,7 @@ insertNotification() ::= <<
     , notification_key
       , created_dt
       , effective_dt
+      , queue_name
       , processing_owner
       , processing_available_dt
       , processing_state
@@ -61,6 +63,7 @@ insertNotification() ::= <<
       , :notification_key
       , :created_dt
       , :effective_dt
+      , :queue_name
       , :processing_owner
       , :processing_available_dt
       , :processing_state
diff --git a/util/src/main/resources/com/ning/billing/util/tag/dao/TagStoreSqlDao.sql.stg b/util/src/main/resources/com/ning/billing/util/tag/dao/TagStoreSqlDao.sql.stg
index 2d78f48..9d7ce5c 100644
--- a/util/src/main/resources/com/ning/billing/util/tag/dao/TagStoreSqlDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/tag/dao/TagStoreSqlDao.sql.stg
@@ -1,6 +1,6 @@
 group TagStoreDao;
 
-save() ::= <<
+batchSaveFromTransaction() ::= <<
   INSERT INTO tags(id, tag_definition_name, object_id, object_type, added_date, added_by)
   VALUES (:id, :tagDefinitionName, :objectId, :objectType, :addedDate, :addedBy)
   ON DUPLICATE KEY UPDATE
diff --git a/util/src/test/java/com/ning/billing/dbi/DBIProvider.java b/util/src/test/java/com/ning/billing/dbi/DBIProvider.java
index a4a7b61..d83c033 100644
--- a/util/src/test/java/com/ning/billing/dbi/DBIProvider.java
+++ b/util/src/test/java/com/ning/billing/dbi/DBIProvider.java
@@ -25,13 +25,14 @@ import com.ning.jdbi.metrics.MetricsTimingCollector;
 import com.ning.jdbi.metrics.SqlJdbiGroupStrategy;
 import com.yammer.metrics.core.MetricsRegistry;
 import org.skife.jdbi.v2.DBI;
+import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.TimingCollector;
 import org.skife.jdbi.v2.logging.Log4JLog;
 import org.skife.jdbi.v2.tweak.SQLLog;
 
 import java.util.concurrent.TimeUnit;
 
-public class DBIProvider implements Provider<DBI>
+public class DBIProvider implements Provider<IDBI>
 {
     private final MetricsRegistry metricsRegistry;
     private final DbiConfig config;
@@ -44,7 +45,7 @@ public class DBIProvider implements Provider<DBI>
     }
 
     @Override
-    public DBI get()
+    public IDBI get()
     {
         final BoneCPConfig dbConfig = new BoneCPConfig();
         dbConfig.setJdbcUrl(config.getJdbcUrl());
@@ -54,7 +55,7 @@ public class DBIProvider implements Provider<DBI>
         dbConfig.setMaxConnectionsPerPartition(config.getMaxActive());
         dbConfig.setConnectionTimeout(config.getConnectionTimeout().getPeriod(), config.getConnectionTimeout().getUnit());
         dbConfig.setPartitionCount(1);
-        dbConfig.setDefaultTransactionIsolation("READ_COMMITTED");
+        dbConfig.setDefaultTransactionIsolation("REPEATABLE_READ");
         dbConfig.setDisableJMX(false);
 
         final BoneCPDataSource ds = new BoneCPDataSource(dbConfig);
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 be53073..1e949a2 100644
--- a/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
+++ b/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
@@ -114,7 +114,7 @@ public class MysqlTestingHelper
         }
     }
 
-    public DBI getDBI()
+    public IDBI getDBI()
     {
         final String dbiString = "jdbc:mysql://localhost:" + port + "/" + DB_NAME + "?createDatabaseIfNotExist=true";
         return new DBI(dbiString, USERNAME, PASSWORD);
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 7698697..72fd8f4 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
@@ -19,6 +19,8 @@ package com.ning.billing.util.clock;
 import com.ning.billing.catalog.api.Duration;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -26,6 +28,8 @@ import java.util.List;
 // STEPH should really be in tests but not accessible from other sub modules
 public class ClockMock extends DefaultClock {
 
+    private static final Logger log = LoggerFactory.getLogger(ClockMock.class);
+
     private enum DeltaType {
         DELTA_NONE,
         DELTA_DURATION,
@@ -54,35 +58,47 @@ public class ClockMock extends DefaultClock {
         return getNow(DateTimeZone.UTC);
     }
 
+    private void logClockAdjustement(DateTime prev, DateTime next) {
+        log.info(String.format("            ************      ADJUSTING CLOCK FROM %s to %s     ********************", prev, next));
+    }
+
     public synchronized void setDeltaFromReality(Duration delta, long epsilon) {
+        DateTime prev = getUTCNow();
         deltaType = DeltaType.DELTA_DURATION;
         deltaFromRealityDuration = new ArrayList<Duration>();
         deltaFromRealityDuration.add(delta);
         deltaFromRealitDurationEpsilon = epsilon;
         deltaFromRealityMs = 0;
+        logClockAdjustement(prev, getUTCNow());
     }
 
     public synchronized void addDeltaFromReality(Duration delta) {
+        DateTime prev = getUTCNow();
         if (deltaType != DeltaType.DELTA_DURATION) {
             throw new RuntimeException("ClockMock should be set with type DELTA_DURATION");
         }
         deltaFromRealityDuration.add(delta);
+        logClockAdjustement(prev, getUTCNow());
     }
 
     public synchronized void setDeltaFromReality(long delta) {
+        DateTime prev = getUTCNow();
         deltaType = DeltaType.DELTA_ABS;
         deltaFromRealityDuration = null;
         deltaFromRealitDurationEpsilon = 0;
         deltaFromRealityMs = delta;
+        logClockAdjustement(prev, getUTCNow());
     }
 
     public synchronized void addDeltaFromReality(long delta) {
+        DateTime prev = getUTCNow();
         if (deltaType != DeltaType.DELTA_ABS) {
             throw new RuntimeException("ClockMock should be set with type DELTA_ABS");
         }
         deltaFromRealityDuration = null;
         deltaFromRealitDurationEpsilon = 0;
         deltaFromRealityMs += delta;
+        logClockAdjustement(prev, getUTCNow());
     }
 
     public synchronized void resetDeltaFromReality() {
diff --git a/util/src/test/java/com/ning/billing/util/customfield/TestFieldStore.java b/util/src/test/java/com/ning/billing/util/customfield/TestFieldStore.java
index 3b8016f..4512a5d 100644
--- a/util/src/test/java/com/ning/billing/util/customfield/TestFieldStore.java
+++ b/util/src/test/java/com/ning/billing/util/customfield/TestFieldStore.java
@@ -20,21 +20,15 @@ import java.io.IOException;
 import java.util.UUID;
 import org.apache.commons.io.IOUtils;
 import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.Transaction;
+import org.skife.jdbi.v2.TransactionStatus;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.Stage;
 import com.ning.billing.dbi.MysqlTestingHelper;
-import com.ning.billing.util.customfield.DefaultFieldStore;
-import com.ning.billing.util.customfield.FieldStore;
 import com.ning.billing.util.customfield.dao.FieldStoreDao;
-import com.ning.billing.util.eventbus.DefaultEventBusService;
-import com.ning.billing.util.eventbus.EventBusService;
-import com.ning.billing.util.glue.EventBusModule;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.fail;
@@ -70,32 +64,48 @@ public class TestFieldStore {
 
     @Test
     public void testFieldStore() {
-        UUID id = UUID.randomUUID();
-        String objectType = "Test widget";
+        final UUID id = UUID.randomUUID();
+        final String objectType = "Test widget";
 
-        FieldStore fieldStore = new DefaultFieldStore(id, objectType);
+        final FieldStore fieldStore1 = new DefaultFieldStore(id, objectType);
 
         String fieldName = "TestField1";
         String fieldValue = "Kitty Hawk";
-        fieldStore.setValue(fieldName, fieldValue);
+        fieldStore1.setValue(fieldName, fieldValue);
 
         FieldStoreDao fieldStoreDao = dbi.onDemand(FieldStoreDao.class);
-        fieldStoreDao.save(id.toString(), objectType, fieldStore.getEntityList());
+        fieldStoreDao.inTransaction(new Transaction<Void, FieldStoreDao>() {
+            @Override
+            public Void inTransaction(FieldStoreDao transactional,
+                    TransactionStatus status) throws Exception {
+                transactional.batchSaveFromTransaction(id.toString(), objectType, fieldStore1.getEntityList());
+                return null;
+            }
+        });
 
-        fieldStore = DefaultFieldStore.create(id, objectType);
-        fieldStore.add(fieldStoreDao.load(id.toString(), objectType));
 
-        assertEquals(fieldStore.getValue(fieldName), fieldValue);
+        final FieldStore fieldStore2 = DefaultFieldStore.create(id, objectType);
+        fieldStore2.add(fieldStoreDao.load(id.toString(), objectType));
 
-        fieldValue = "Cape Canaveral";
-        fieldStore.setValue(fieldName, fieldValue);
-        assertEquals(fieldStore.getValue(fieldName), fieldValue);
-        fieldStoreDao.save(id.toString(), objectType, fieldStore.getEntityList());
-
-        fieldStore = DefaultFieldStore.create(id, objectType);
-        assertEquals(fieldStore.getValue(fieldName), null);
-        fieldStore.add(fieldStoreDao.load(id.toString(), objectType));
+        assertEquals(fieldStore2.getValue(fieldName), fieldValue);
 
-        assertEquals(fieldStore.getValue(fieldName), fieldValue);
+        fieldValue = "Cape Canaveral";
+        fieldStore2.setValue(fieldName, fieldValue);
+        assertEquals(fieldStore2.getValue(fieldName), fieldValue);
+        fieldStoreDao.inTransaction(new Transaction<Void, FieldStoreDao>() {
+            @Override
+            public Void inTransaction(FieldStoreDao transactional,
+                    TransactionStatus status) throws Exception {
+                transactional.batchSaveFromTransaction(id.toString(), objectType, fieldStore2.getEntityList());
+                return null;
+            }
+        });
+
+
+        final FieldStore fieldStore3 = DefaultFieldStore.create(id, objectType);
+        assertEquals(fieldStore3.getValue(fieldName), null);
+        fieldStore3.add(fieldStoreDao.load(id.toString(), objectType));
+
+        assertEquals(fieldStore3.getValue(fieldName), fieldValue);
     }
 }
diff --git a/util/src/test/java/com/ning/billing/util/globalLocker/MockGlobalLocker.java b/util/src/test/java/com/ning/billing/util/globalLocker/MockGlobalLocker.java
new file mode 100644
index 0000000..18336df
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/globalLocker/MockGlobalLocker.java
@@ -0,0 +1,35 @@
+/*
+ * 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.util.globalLocker;
+
+public class MockGlobalLocker implements GlobalLocker {
+
+    @Override
+    public GlobalLock lockWithNumberOfTries(LockerService service,
+            String lockKey, int retry) {
+        return new GlobalLock() {
+            @Override
+            public void release() {
+            }
+        };
+    }
+
+    @Override
+    public Boolean isFree(LockerService service, String lockKey) {
+        return Boolean.TRUE;
+    }
+}
diff --git a/util/src/test/java/com/ning/billing/util/globalLocker/TestMysqlGlobalLocker.java b/util/src/test/java/com/ning/billing/util/globalLocker/TestMysqlGlobalLocker.java
new file mode 100644
index 0000000..b797522
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/globalLocker/TestMysqlGlobalLocker.java
@@ -0,0 +1,115 @@
+/*
+ * 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.util.globalLocker;
+
+import java.io.IOException;
+import java.util.UUID;
+
+import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.TransactionCallback;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.util.globalLocker.GlobalLocker.LockerService;
+
+@Guice(modules=TestMysqlGlobalLocker.TestMysqlGlobalLockerModule.class)
+public class TestMysqlGlobalLocker {
+
+    @Inject
+    private IDBI dbi;
+
+    @Inject
+    private MysqlTestingHelper helper;
+
+    @BeforeClass(alwaysRun=true)
+    public void setup() throws IOException  {
+        helper.startMysql();
+        createSimpleTable(dbi);
+    }
+
+    @AfterClass(alwaysRun=true)
+    public void tearDown() {
+        helper.stopMysql();
+    }
+
+    // Used as a manual test to validate the simple DAO by stepping through that locking is done and release correctly
+    @Test(groups= "slow", enabled = true)
+    public void testSimpleLocking() {
+
+        final String lockName = UUID.randomUUID().toString();
+
+        GlobalLocker locker = new  MySqlGlobalLocker(dbi);
+        GlobalLock lock = locker.lockWithNumberOfTries(LockerService.INVOICE, lockName, 3);
+
+        dbi.inTransaction(new TransactionCallback<Void>() {
+            @Override
+            public Void inTransaction(Handle conn, TransactionStatus status)
+                    throws Exception {
+                conn.execute("insert into dummy (dummy_id) values ('" + UUID.randomUUID().toString()  + "')");
+                return null;
+            }
+        });
+        Assert.assertEquals(locker.isFree(LockerService.INVOICE, lockName), Boolean.FALSE);
+
+        boolean gotException = false;
+        try {
+            locker.lockWithNumberOfTries(LockerService.INVOICE, lockName, 1);
+        } catch (LockFailedException e) {
+            gotException = true;
+        }
+        Assert.assertTrue(gotException);
+
+        lock.release();
+
+        Assert.assertEquals(locker.isFree(LockerService.INVOICE, lockName), Boolean.TRUE);
+    }
+
+    private void createSimpleTable(IDBI dbi) {
+        dbi.inTransaction(new TransactionCallback<Void>() {
+
+            @Override
+            public Void inTransaction(Handle h, TransactionStatus status)
+                    throws Exception {
+                h.execute("create table dummy " +
+                        "(id int(11) unsigned NOT NULL AUTO_INCREMENT, " +
+                        "dummy_id char(36) NOT NULL, " +
+                        "PRIMARY KEY(id)" +
+                		") ENGINE=innodb;");
+                return null;
+            }
+        });
+    }
+
+    public final static class TestMysqlGlobalLockerModule extends AbstractModule {
+
+        @Override
+        protected void configure() {
+            MysqlTestingHelper helper = new MysqlTestingHelper();
+            bind(MysqlTestingHelper.class).toInstance(helper);
+            final IDBI dbi = helper.getDBI();
+            bind(IDBI.class).toInstance(dbi);
+        }
+    }
+}
diff --git a/util/src/test/java/com/ning/billing/util/notificationq/dao/TestNotificationSqlDao.java b/util/src/test/java/com/ning/billing/util/notificationq/dao/TestNotificationSqlDao.java
index 89dfd76..cb01e00 100644
--- a/util/src/test/java/com/ning/billing/util/notificationq/dao/TestNotificationSqlDao.java
+++ b/util/src/test/java/com/ning/billing/util/notificationq/dao/TestNotificationSqlDao.java
@@ -16,49 +16,42 @@
 
 package com.ning.billing.util.notificationq.dao;
 
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
-
 import java.io.IOException;
 import java.sql.SQLException;
 import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.atomic.AtomicInteger;
-
 import org.apache.commons.io.IOUtils;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
-import org.skife.config.ConfigurationObjectFactory;
 import org.skife.jdbi.v2.DBI;
 import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.tweak.HandleCallback;
 import org.testng.Assert;
-import org.testng.annotations.AfterClass;
 import org.testng.annotations.AfterSuite;
-import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeSuite;
 import org.testng.annotations.BeforeTest;
 import org.testng.annotations.Guice;
 import org.testng.annotations.Test;
-
 import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
-import com.ning.billing.dbi.DBIProvider;
-import com.ning.billing.dbi.DbiConfig;
 import com.ning.billing.dbi.MysqlTestingHelper;
 import com.ning.billing.util.notificationq.DefaultNotification;
 import com.ning.billing.util.notificationq.Notification;
 import com.ning.billing.util.notificationq.NotificationLifecycle.NotificationLifecycleState;
 import com.ning.billing.util.notificationq.dao.NotificationSqlDao.NotificationSqlMapper;
 
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
 @Guice(modules = TestNotificationSqlDao.TestNotificationSqlDaoModule.class)
 public class TestNotificationSqlDao {
 
     private static AtomicInteger sequenceId = new AtomicInteger();
 
     @Inject
-    private DBI dbi;
+    private IDBI dbi;
 
     @Inject
     MysqlTestingHelper helper;
@@ -110,12 +103,12 @@ public class TestNotificationSqlDao {
 
         String notificationKey = UUID.randomUUID().toString();
         DateTime effDt = new DateTime();
-        Notification notif = new DefaultNotification(notificationKey, effDt);
+        Notification notif = new DefaultNotification("testBasic",notificationKey, effDt);
         dao.insertNotification(notif);
 
         Thread.sleep(1000);
         DateTime now = new DateTime();
-        List<Notification> notifications = dao.getReadyNotifications(now.toDate(), 3);
+        List<Notification> notifications = dao.getReadyNotifications(now.toDate(), 3, "testBasic");
         assertNotNull(notifications);
         assertEquals(notifications.size(), 1);
 
@@ -127,23 +120,23 @@ public class TestNotificationSqlDao {
         assertEquals(notification.getNextAvailableDate(), null);
 
         DateTime nextAvailable = now.plusMinutes(5);
-        int res = dao.claimNotification(ownerId, nextAvailable.toDate(), notification.getId().toString(), now.toDate());
+        int res = dao.claimNotification(ownerId, nextAvailable.toDate(), notification.getId(), now.toDate());
         assertEquals(res, 1);
-        dao.insertClaimedHistory(sequenceId.incrementAndGet(), ownerId, now.toDate(), notification.getId().toString());
+        dao.insertClaimedHistory(sequenceId.incrementAndGet(), ownerId, now.toDate(), notification.getUUID().toString());
 
-        notification = fetchNotification(notification.getId().toString());
+        notification = fetchNotification(notification.getUUID().toString());
         assertEquals(notification.getNotificationKey(), notificationKey);
         validateDate(notification.getEffectiveDate(), effDt);
         assertEquals(notification.getOwner().toString(), ownerId);
         assertEquals(notification.getProcessingState(), NotificationLifecycleState.IN_PROCESSING);
         validateDate(notification.getNextAvailableDate(), nextAvailable);
 
-        dao.clearNotification(notification.getId().toString(), ownerId);
+        dao.clearNotification(notification.getId(), ownerId);
 
-        notification = fetchNotification(notification.getId().toString());
+        notification = fetchNotification(notification.getUUID().toString());
         assertEquals(notification.getNotificationKey(), notificationKey);
         validateDate(notification.getEffectiveDate(), effDt);
-        assertEquals(notification.getOwner(), null);
+        //assertEquals(notification.getOwner(), null);
         assertEquals(notification.getProcessingState(), NotificationLifecycleState.PROCESSED);
         validateDate(notification.getNextAvailableDate(), nextAvailable);
 
@@ -155,10 +148,12 @@ public class TestNotificationSqlDao {
             @Override
             public Notification withHandle(Handle handle) throws Exception {
                 Notification res = handle.createQuery("   select" +
-                		" notification_id" +
+                        " id " +
+                		", notification_id" +
                 		", notification_key" +
                 		", created_dt" +
                 		", effective_dt" +
+                		", queue_name" +
                 		", processing_owner" +
                 		", processing_available_dt" +
                 		", processing_state" +
@@ -199,8 +194,8 @@ public class TestNotificationSqlDao {
 
             final MysqlTestingHelper helper = new MysqlTestingHelper();
             bind(MysqlTestingHelper.class).toInstance(helper);
-            DBI dbi = helper.getDBI();
-            bind(DBI.class).toInstance(dbi);
+            IDBI dbi = helper.getDBI();
+            bind(IDBI.class).toInstance(dbi);
 
             /*
             bind(DBI.class).toProvider(DBIProvider.class).asEagerSingleton();
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 b141310..e1da366 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
@@ -23,7 +23,6 @@ import java.util.List;
 import java.util.TreeSet;
 
 import org.joda.time.DateTime;
-import org.skife.jdbi.v2.DBI;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 
 import com.ning.billing.util.clock.Clock;
@@ -33,7 +32,7 @@ import com.ning.billing.util.notificationq.NotificationQueueService.Notification
 public class MockNotificationQueue extends NotificationQueueBase implements NotificationQueue {
 
 
-    private TreeSet<Notification> notifications;
+    private final TreeSet<Notification> notifications;
 
     public MockNotificationQueue(final Clock clock,  final String svcName, final String queueName, final NotificationQueueHandler handler, final NotificationConfig config) {
         super(clock, svcName, queueName, handler, config);
@@ -53,7 +52,7 @@ public class MockNotificationQueue extends NotificationQueueBase implements Noti
     public void recordFutureNotificationFromTransaction(
             Transmogrifier transactionalDao, DateTime futureNotificationTime,
             NotificationKey notificationKey) {
-        Notification notification = new DefaultNotification(notificationKey.toString(), futureNotificationTime);
+        Notification notification = new DefaultNotification("MockQueue", notificationKey.toString(), futureNotificationTime);
         synchronized(notifications) {
             notifications.add(notification);
         }
@@ -76,7 +75,7 @@ public class MockNotificationQueue extends NotificationQueueBase implements Noti
             }
             for (Notification cur : readyNotifications) {
                 handler.handleReadyNotification(cur.getNotificationKey());
-                DefaultNotification processedNotification = new DefaultNotification(cur.getId(), hostname, clock.getUTCNow().plus(config.getDaoClaimTimeMs()), NotificationLifecycleState.PROCESSED, 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);
 
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 2e3bb3c..eac2d78 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
@@ -16,8 +16,9 @@
 
 package com.ning.billing.util.notificationq;
 
+import static com.jayway.awaitility.Awaitility.await;
+import static java.util.concurrent.TimeUnit.MINUTES;
 import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertTrue;
 
 import java.io.IOException;
 import java.sql.SQLException;
@@ -25,21 +26,19 @@ import java.util.Collection;
 import java.util.Map;
 import java.util.TreeMap;
 import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
 
 import org.apache.commons.io.IOUtils;
 import org.joda.time.DateTime;
-import org.skife.config.ConfigurationObjectFactory;
-import org.skife.jdbi.v2.DBI;
 import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.TransactionStatus;
 import org.skife.jdbi.v2.tweak.HandleCallback;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-
-import org.testng.annotations.AfterSuite;
-import org.testng.annotations.AfterTest;
-import org.testng.annotations.BeforeClass;
+import org.testng.Assert;
 import org.testng.annotations.BeforeSuite;
 import org.testng.annotations.BeforeTest;
 import org.testng.annotations.Guice;
@@ -49,8 +48,8 @@ import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
 import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
-import com.ning.billing.dbi.DBIProvider;
-import com.ning.billing.dbi.DbiConfig;
+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;
 import com.ning.billing.util.clock.ClockMock;
@@ -59,370 +58,367 @@ import com.ning.billing.util.notificationq.dao.NotificationSqlDao;
 
 @Guice(modules = TestNotificationQueue.TestNotificationQueueModule.class)
 public class TestNotificationQueue {
-
-    private final static Logger log = LoggerFactory.getLogger(TestNotificationQueue.class);
-
-    @Inject
-    private DBI dbi;
-
+	Logger log = LoggerFactory.getLogger(TestNotificationQueue.class);
     @Inject
-    MysqlTestingHelper helper;
-
-    @Inject
-    private Clock clock;
-
-    private DummySqlTest dao;
-
-   // private NotificationQueue queue;
-
-    private void startMysql() throws IOException, ClassNotFoundException, SQLException {
-        final String ddl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
-        final String testDdl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl_test.sql"));
-        helper.startMysql();
-        helper.initDb(ddl);
-        helper.initDb(testDdl);
-    }
-
-    @BeforeSuite(alwaysRun = true)
-    public void setup() throws Exception {
-        startMysql();
-        dao = dbi.onDemand(DummySqlTest.class);
-    }
-
-    @BeforeTest
-    public void beforeTest() {
-        dbi.withHandle(new HandleCallback<Void>() {
-
-            @Override
-            public Void withHandle(Handle handle) throws Exception {
-                handle.execute("delete from notifications");
-                handle.execute("delete from claimed_notifications");
-                handle.execute("delete from dummy");
-                return null;
-            }
-        });
-        // Reset time to real value
-        ((ClockMock) clock).resetDeltaFromReality();
-    }
-
-
-
-    /**
-     * Verify that we can call start/stop on a disabled queue and that both start/stop callbacks are called
-     *
-     * @throws InterruptedException
-     */
-    @Test
-    public void testSimpleQueueDisabled() throws InterruptedException {
-
-        final TestStartStop testStartStop = new TestStartStop(false, false);
-        DefaultNotificationQueue queue = new DefaultNotificationQueue(dbi, clock, "test-svc", "dead",
-                new NotificationQueueHandler() {
-                    @Override
-                    public void handleReadyNotification(String notificationKey) {
-                    }
-                    @Override
-                    public void completedQueueStop() {
-                        testStartStop.stopped();
-                    }
-                    @Override
-                    public void completedQueueStart() {
-                        testStartStop.started();
-                    }
-                },
-                getNotificationConfig(true, 100, 1, 10000));
-
-        executeTest(testStartStop, queue, new WithTest() {
+    private IDBI dbi;
+
+	@Inject
+	MysqlTestingHelper helper;
+
+	@Inject
+	private Clock clock;
+
+	private DummySqlTest dao;
+
+	private int eventsReceived;
+
+	// private NotificationQueue queue;
+
+	private void startMysql() throws IOException, ClassNotFoundException, SQLException {
+		final String ddl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
+		final String testDdl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl_test.sql"));
+		helper.startMysql();
+		helper.initDb(ddl);
+		helper.initDb(testDdl);
+	}
+
+	@BeforeSuite(alwaysRun = true)
+	public void setup() throws Exception {
+		startMysql();
+		dao = dbi.onDemand(DummySqlTest.class);
+	}
+
+	@BeforeTest
+	public void beforeTest() {
+		dbi.withHandle(new HandleCallback<Void>() {
+
+			@Override
+			public Void withHandle(Handle handle) throws Exception {
+				handle.execute("delete from notifications");
+				handle.execute("delete from claimed_notifications");
+				handle.execute("delete from dummy");
+				return null;
+			}
+		});
+		// Reset time to real value
+		((ClockMock) clock).resetDeltaFromReality();
+	}
+
+
+
+	/**
+	 * Test that we can post a notification in the future from a transaction and get the notification
+	 * callback with the correct key when the time is ready
+	 * @throws Exception
+	 */
+	@Test(groups={"fast"}, enabled = true)
+	public void testSimpleNotification() throws Exception {
+
+		final Map<String, Boolean> expectedNotifications = new TreeMap<String, Boolean>();
+
+		final DefaultNotificationQueue queue = new DefaultNotificationQueue(dbi, clock, "test-svc", "foo",
+				new NotificationQueueHandler() {
+			@Override
+			public void handleReadyNotification(String notificationKey) {
+				synchronized (expectedNotifications) {
+	            	log.info("Handler received key: " + notificationKey);
+
+					expectedNotifications.put(notificationKey.toString(), Boolean.TRUE);
+					expectedNotifications.notify();
+				}
+			}
+		},
+		getNotificationConfig(false, 100, 1, 10000));
+
+
+		queue.startQueue();
+
+		final UUID key = UUID.randomUUID();
+		final DummyObject obj = new DummyObject("foo", key);
+		final DateTime now = new DateTime();
+		final DateTime readyTime = now.plusMillis(2000);
+		final NotificationKey notificationKey = new NotificationKey() {
+			@Override
+			public String toString() {
+				return key.toString();
+			}
+		};
+		expectedNotifications.put(notificationKey.toString(), Boolean.FALSE);
+
+
+		// Insert dummy to be processed in 2 sec'
+		dao.inTransaction(new Transaction<Void, DummySqlTest>() {
+			@Override
+			public Void inTransaction(DummySqlTest transactional,
+					TransactionStatus status) throws Exception {
+
+				transactional.insertDummy(obj);
+				queue.recordFutureNotificationFromTransaction(transactional,
+						readyTime, notificationKey);
+            	log.info("Posted key: " + notificationKey);
+
+				return null;
+			}
+		});
+
+		// Move time in the future after the notification effectiveDate
+		((ClockMock) clock).setDeltaFromReality(3000);
+
+		// Notification should have kicked but give it at least a sec' for thread scheduling
+	    await().atMost(1, MINUTES).until(new Callable<Boolean>() {
             @Override
-            public void test(final DefaultNotificationQueue readyQueue) throws InterruptedException {
-                // Do nothing
+            public Boolean call() throws Exception {
+                return expectedNotifications.get(notificationKey.toString());
             }
         });
-        assertTrue(true);
-    }
-
-    /**
-     * Test that we can post a notification in the future from a transaction and get the notification
-     * callback with the correct key when the time is ready
-     *
-     * @throws InterruptedException
-     */
-    @Test
-    public void testSimpleNotification() throws InterruptedException {
-
-        final Map<String, Boolean> expectedNotifications = new TreeMap<String, Boolean>();
-
-        final TestStartStop testStartStop = new TestStartStop(false, false);
-        DefaultNotificationQueue queue = new DefaultNotificationQueue(dbi, clock, "test-svc", "foo",
-                new NotificationQueueHandler() {
-                    @Override
-                    public void handleReadyNotification(String notificationKey) {
-                        synchronized (expectedNotifications) {
-                            expectedNotifications.put(notificationKey, Boolean.TRUE);
-                            expectedNotifications.notify();
-                        }
-                    }
-                    @Override
-                    public void completedQueueStop() {
-                        testStartStop.stopped();
-                    }
-                    @Override
-                    public void completedQueueStart() {
-                        testStartStop.started();
-                    }
-                },
-                getNotificationConfig(false, 100, 1, 10000));
-
-
-        executeTest(testStartStop, queue, new WithTest() {
-            @Override
-            public void test(final DefaultNotificationQueue readyQueue) throws InterruptedException {
-
-                final UUID key = UUID.randomUUID();
-                final DummyObject obj = new DummyObject("foo", key);
-                final DateTime now = new DateTime();
-                final DateTime readyTime = now.plusMillis(2000);
-                final NotificationKey notificationKey = new NotificationKey() {
-                    @Override
-                    public String toString() {
-                        return key.toString();
-                    }
-                };
-                expectedNotifications.put(notificationKey.toString(), Boolean.FALSE);
-
-
-                // Insert dummy to be processed in 2 sec'
-                dao.inTransaction(new Transaction<Void, DummySqlTest>() {
-                    @Override
-                    public Void inTransaction(DummySqlTest transactional,
-                            TransactionStatus status) throws Exception {
-
-                        transactional.insertDummy(obj);
-                        readyQueue.recordFutureNotificationFromTransaction(transactional,
-                                readyTime, notificationKey);
-                        return null;
-                    }
-                });
-
-                // Move time in the future after the notification effectiveDate
-                ((ClockMock) clock).setDeltaFromReality(3000);
-
-                // Notification should have kicked but give it at least a sec' for thread scheduling
-                int nbTry = 1;
-                boolean success = false;
-                do {
-                    synchronized(expectedNotifications) {
-                        if (expectedNotifications.get(notificationKey.toString())) {
-                            success = true;
-                            break;
-                        }
-                        expectedNotifications.wait(1000);
-                    }
-                } while (nbTry-- > 0);
-                assertEquals(success, true);
-            }
-        });
-    }
-
-    @Test
-    public void testManyNotifications() throws InterruptedException {
-        final Map<String, Boolean> expectedNotifications = new TreeMap<String, Boolean>();
-
-        final TestStartStop testStartStop = new TestStartStop(false, false);
-        DefaultNotificationQueue queue = new DefaultNotificationQueue(dbi, clock, "test-svc", "many",
-                new NotificationQueueHandler() {
-                    @Override
-                    public void handleReadyNotification(String notificationKey) {
-                        synchronized (expectedNotifications) {
-                            expectedNotifications.put(notificationKey, Boolean.TRUE);
-                            expectedNotifications.notify();
-                        }
-                    }
-                    @Override
-                    public void completedQueueStop() {
-                        testStartStop.stopped();
-                    }
-                    @Override
-                    public void completedQueueStart() {
-                        testStartStop.started();
-                    }
-                },
-                getNotificationConfig(false, 100, 10, 10000));
-
-
-        executeTest(testStartStop, queue, new WithTest() {
-            @Override
-            public void test(final DefaultNotificationQueue readyQueue) throws InterruptedException {
-
-                final DateTime now = clock.getUTCNow();
-                final int MAX_NOTIFICATIONS = 100;
-                for (int i = 0; i < MAX_NOTIFICATIONS; i++) {
-
-                    final int nextReadyTimeIncrementMs = 1000;
-
-                    final UUID key = UUID.randomUUID();
-                    final DummyObject obj = new DummyObject("foo", key);
-                    final int currentIteration = i;
-
-                    final NotificationKey notificationKey = new NotificationKey() {
-                        @Override
-                        public String toString() {
-                            return key.toString();
-                        }
-                    };
-                    expectedNotifications.put(notificationKey.toString(), Boolean.FALSE);
-
-                    dao.inTransaction(new Transaction<Void, DummySqlTest>() {
-                        @Override
-                        public Void inTransaction(DummySqlTest transactional,
-                                TransactionStatus status) throws Exception {
-
-                            transactional.insertDummy(obj);
-                            readyQueue.recordFutureNotificationFromTransaction(transactional,
-                                    now.plus((currentIteration + 1) * nextReadyTimeIncrementMs), notificationKey);
-                            return null;
-                        }
-                    });
-
-                    // Move time in the future after the notification effectiveDate
-                    if (i == 0) {
-                        ((ClockMock) clock).setDeltaFromReality(nextReadyTimeIncrementMs);
-                    } else {
-                        ((ClockMock) clock).addDeltaFromReality(nextReadyTimeIncrementMs);
-                    }
-                }
-
-                // Wait a little longer since there are a lot of callback that need to happen
-                int nbTry = MAX_NOTIFICATIONS + 1;
-                boolean success = false;
-                do {
-                    synchronized(expectedNotifications) {
-
-                        Collection<Boolean> completed =  Collections2.filter(expectedNotifications.values(), new Predicate<Boolean>() {
-                            @Override
-                            public boolean apply(Boolean input) {
-                                return input;
-                            }
-                        });
-
-                        if (completed.size() == MAX_NOTIFICATIONS) {
-                            success = true;
-                            break;
-                        }
-                        //log.debug(String.format("BEFORE WAIT : Got %d notifications at time %s (real time %s)", completed.size(), clock.getUTCNow(), new DateTime()));
-                        expectedNotifications.wait(1000);
-                    }
-                } while (nbTry-- > 0);
-                assertEquals(success, true);
-            }
-        });
-    }
-
 
-    NotificationConfig getNotificationConfig(final boolean off,
-            final long sleepTime, final int maxReadyEvents, final long claimTimeMs) {
-        return new NotificationConfig() {
+	Assert.assertTrue(expectedNotifications.get(notificationKey.toString()));
+	}
+
+	@Test
+	public void testManyNotifications() throws InterruptedException {
+		final Map<String, Boolean> expectedNotifications = new TreeMap<String, Boolean>();
+
+		final DefaultNotificationQueue queue = new DefaultNotificationQueue(dbi, clock, "test-svc", "many",
+				new NotificationQueueHandler() {
+			@Override
+			public void handleReadyNotification(String notificationKey) {
+				synchronized (expectedNotifications) {
+					expectedNotifications.put(notificationKey, Boolean.TRUE);
+					expectedNotifications.notify();
+				}
+			}
+		},
+		getNotificationConfig(false, 100, 10, 10000));
+
+
+		queue.startQueue();
+
+		final DateTime now = clock.getUTCNow();
+		final int MAX_NOTIFICATIONS = 100;
+		for (int i = 0; i < MAX_NOTIFICATIONS; i++) {
+
+			final int nextReadyTimeIncrementMs = 1000;
+
+			final UUID key = UUID.randomUUID();
+			final DummyObject obj = new DummyObject("foo", key);
+			final int currentIteration = i;
+
+			final NotificationKey notificationKey = new NotificationKey() {
+				@Override
+				public String toString() {
+					return key.toString();
+				}
+			};
+			expectedNotifications.put(notificationKey.toString(), Boolean.FALSE);
+
+			dao.inTransaction(new Transaction<Void, DummySqlTest>() {
+				@Override
+				public Void inTransaction(DummySqlTest transactional,
+						TransactionStatus status) throws Exception {
+
+					transactional.insertDummy(obj);
+					queue.recordFutureNotificationFromTransaction(transactional,
+							now.plus((currentIteration + 1) * nextReadyTimeIncrementMs), notificationKey);
+					return null;
+				}
+			});
+
+			// Move time in the future after the notification effectiveDate
+			if (i == 0) {
+				((ClockMock) clock).setDeltaFromReality(nextReadyTimeIncrementMs);
+			} else {
+				((ClockMock) clock).addDeltaFromReality(nextReadyTimeIncrementMs);
+			}
+		}
+
+		// Wait a little longer since there are a lot of callback that need to happen
+		int nbTry = MAX_NOTIFICATIONS + 1;
+		boolean success = false;
+		do {
+			synchronized(expectedNotifications) {
+
+				Collection<Boolean> completed =  Collections2.filter(expectedNotifications.values(), new Predicate<Boolean>() {
+					@Override
+					public boolean apply(Boolean input) {
+						return input;
+					}
+				});
+
+				if (completed.size() == MAX_NOTIFICATIONS) {
+					success = true;
+					break;
+				}
+				//log.debug(String.format("BEFORE WAIT : Got %d notifications at time %s (real time %s)", completed.size(), clock.getUTCNow(), new DateTime()));
+				expectedNotifications.wait(1000);
+			}
+		} while (nbTry-- > 0);
+		assertEquals(success, true);
+
+	}
+
+	/**
+	 * Test that we can post a notification in the future from a transaction and get the notification
+	 * callback with the correct key when the time is ready
+	 * @throws Exception
+	 */
+	@Test(groups={"fast"}, enabled = true)
+	public void testMultipleHandlerNotification() throws Exception {
+
+		final Map<String, Boolean> expectedNotificationsFred = new TreeMap<String, Boolean>();
+		final Map<String, Boolean> expectedNotificationsBarney = new TreeMap<String, Boolean>();
+
+		NotificationQueueService notificationQueueService = new DefaultNotificationQueueService(dbi,  clock);
+
+		NotificationConfig config=new NotificationConfig() {
             @Override
             public boolean isNotificationProcessingOff() {
-                return off;
+                return false;
             }
             @Override
             public long getNotificationSleepTimeMs() {
-                return sleepTime;
+                return 10;
             }
             @Override
             public int getDaoMaxReadyEvents() {
-                return maxReadyEvents;
+                return 1;
             }
             @Override
             public long getDaoClaimTimeMs() {
-                return claimTimeMs;
-            }
-        };
-    }
-
-    private static class TestStartStop {
-        private boolean started;
-        private boolean stopped;
-
-        public TestStartStop(boolean started, boolean stopped) {
-            super();
-            this.started = started;
-            this.stopped = stopped;
-        }
-
-        public void started() {
-            synchronized(this) {
-                started = true;
-                notify();
-            }
-        }
-
-        public void stopped() {
-            synchronized(this) {
-                stopped = true;
-                notify();
-            }
-        }
-
-        public boolean waitForStartComplete(int timeoutMs) throws InterruptedException {
-            return waitForEventCompletion(timeoutMs, true);
-        }
-
-        public boolean waitForStopComplete(int timeoutMs) throws InterruptedException {
-            return waitForEventCompletion(timeoutMs, false);
-        }
-
-        private boolean waitForEventCompletion(int timeoutMs, boolean start) throws InterruptedException {
-            DateTime init = new DateTime();
-            synchronized(this) {
-                while (! ((start ? started : stopped))) {
-                    wait(timeoutMs);
-                    if (init.plusMillis(timeoutMs).isAfterNow()) {
-                        break;
-                    }
-                }
+                return 60000;
             }
-            return (start ? started : stopped);
-        }
-    }
+		};
 
-    private interface WithTest {
-        public void test(DefaultNotificationQueue readyQueue) throws InterruptedException;
-    }
 
-    private void executeTest(final TestStartStop testStartStop,
-            DefaultNotificationQueue queue, WithTest test) throws InterruptedException{
-
-        queue.startQueue();
-        boolean started = testStartStop.waitForStartComplete(3000);
-        assertEquals(started, true);
-
-        test.test(queue);
-
-        queue.stopQueue();
-        boolean stopped = testStartStop.waitForStopComplete(3000);
-        assertEquals(stopped, true);
-    }
-
-
-    public static class TestNotificationQueueModule extends AbstractModule {
-        @Override
-        protected void configure() {
-
-            bind(Clock.class).to(ClockMock.class);
+		final NotificationQueue queueFred = notificationQueueService.createNotificationQueue("UtilTest", "Fred", new NotificationQueueHandler() {
+                @Override
+                public void handleReadyNotification(String notificationKey) {
+                	log.info("Fred received key: " + notificationKey);
+                	expectedNotificationsFred.put(notificationKey, Boolean.TRUE);
+                	eventsReceived++;
+                }
+            },
+            config);
 
-            final MysqlTestingHelper helper = new MysqlTestingHelper();
-            bind(MysqlTestingHelper.class).toInstance(helper);
-            DBI dbi = helper.getDBI();
-            bind(DBI.class).toInstance(dbi);
-            /*
+		final NotificationQueue queueBarney = notificationQueueService.createNotificationQueue("UtilTest", "Barney", new NotificationQueueHandler() {
+            @Override
+            public void handleReadyNotification(String notificationKey) {
+             	log.info("Barney received key: " + notificationKey);
+            	expectedNotificationsBarney.put(notificationKey, Boolean.TRUE);
+            	eventsReceived++;
+            }
+        },
+        config);
+
+		queueFred.startQueue();
+//		We don't start Barney so it can never pick up notifications
+
+
+		final UUID key = UUID.randomUUID();
+		final DummyObject obj = new DummyObject("foo", key);
+		final DateTime now = new DateTime();
+		final DateTime readyTime = now.plusMillis(2000);
+		final NotificationKey notificationKeyFred = new NotificationKey() {
+			@Override
+			public String toString() {
+				return "Fred" ;
+			}
+		};
+
+
+		final NotificationKey notificationKeyBarney = new NotificationKey() {
+			@Override
+			public String toString() {
+				return "Barney" ;
+			}
+		};
+
+		expectedNotificationsFred.put(notificationKeyFred.toString(), Boolean.FALSE);
+		expectedNotificationsFred.put(notificationKeyBarney.toString(), Boolean.FALSE);
+
+
+		// Insert dummy to be processed in 2 sec'
+		dao.inTransaction(new Transaction<Void, DummySqlTest>() {
+			@Override
+			public Void inTransaction(DummySqlTest transactional,
+					TransactionStatus status) throws Exception {
+
+				transactional.insertDummy(obj);
+				queueFred.recordFutureNotificationFromTransaction(transactional,
+						readyTime, notificationKeyFred);
+				log.info("posted key: " + notificationKeyFred.toString());
+				queueBarney.recordFutureNotificationFromTransaction(transactional,
+						readyTime, notificationKeyBarney);
+				log.info("posted key: " + notificationKeyBarney.toString());
+
+				return null;
+			}
+		});
+
+		// Move time in the future after the notification effectiveDate
+		((ClockMock) clock).setDeltaFromReality(3000);
+
+		// Note the timeout is short on this test, but expected behaviour is that it times out.
+		// We are checking that the Fred queue does not pick up the Barney event
+		try {
+			await().atMost(5, TimeUnit.SECONDS).until(new Callable<Boolean>() {
+				@Override
+				public Boolean call() throws Exception {
+					return eventsReceived >= 2;
+				}
+			});
+			Assert.fail("There should only have been one event for the queue to pick up - it got more than that");
+		} catch (Exception e) {
+			// expected behavior
+		}
+
+		Assert.assertTrue(expectedNotificationsFred.get(notificationKeyFred.toString()));
+		Assert.assertFalse(expectedNotificationsFred.get(notificationKeyBarney.toString()));
+
+	}
+
+	NotificationConfig getNotificationConfig(final boolean off,
+			final long sleepTime, final int maxReadyEvents, final long claimTimeMs) {
+		return new NotificationConfig() {
+			@Override
+			public boolean isNotificationProcessingOff() {
+				return off;
+			}
+			@Override
+			public long getNotificationSleepTimeMs() {
+				return sleepTime;
+			}
+			@Override
+			public int getDaoMaxReadyEvents() {
+				return maxReadyEvents;
+			}
+			@Override
+			public long getDaoClaimTimeMs() {
+				return claimTimeMs;
+			}
+		};
+	}
+
+
+	public static class TestNotificationQueueModule extends AbstractModule {
+		@Override
+		protected void configure() {
+
+			bind(Clock.class).to(ClockMock.class);
+
+			final MysqlTestingHelper helper = new MysqlTestingHelper();
+			bind(MysqlTestingHelper.class).toInstance(helper);
+			IDBI dbi = helper.getDBI();
+			bind(IDBI.class).toInstance(dbi);
+			IDBI otherDbi = helper.getDBI();
+			bind(IDBI.class).annotatedWith(Names.named("global-lock")).toInstance(otherDbi);
+			/*
             bind(DBI.class).toProvider(DBIProvider.class).asEagerSingleton();
             final DbiConfig config = new ConfigurationObjectFactory(System.getProperties()).build(DbiConfig.class);
             bind(DbiConfig.class).toInstance(config);
-            */
-        }
-    }
+			 */
+		}
+	}
 
 
 }
diff --git a/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDefinitionDao.java b/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDefinitionDao.java
new file mode 100644
index 0000000..f09e851
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDefinitionDao.java
@@ -0,0 +1,61 @@
+/*
+ * 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.util.tag.dao;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.util.api.TagDefinitionApiException;
+import com.ning.billing.util.tag.DefaultTagDefinition;
+import com.ning.billing.util.tag.TagDefinition;
+
+public class MockTagDefinitionDao implements TagDefinitionDao {
+    private final Map<String, TagDefinition> tags = new ConcurrentHashMap<String, TagDefinition>();
+
+    @Override
+    public List<TagDefinition> getTagDefinitions() {
+        return new ArrayList<TagDefinition>(tags.values());
+    }
+
+    @Override
+    public TagDefinition getByName(String definitionName) {
+        return tags.get(definitionName);
+    }
+
+    @Override
+    public TagDefinition create(String definitionName, String description, String createdBy) throws TagDefinitionApiException {
+        TagDefinition tag = new DefaultTagDefinition(UUID.randomUUID(), definitionName, description, createdBy, new DateTime());
+
+        tags.put(definitionName, tag);
+        return tag;
+    }
+
+    @Override
+    public void deleteAllTagsForDefinition(String definitionName) throws TagDefinitionApiException {
+        tags.remove(definitionName);
+    }
+
+    @Override
+    public void deleteTagDefinition(String definitionName) throws TagDefinitionApiException {
+        tags.remove(definitionName);
+    }
+}
diff --git a/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java b/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java
index f764e0c..c913791 100644
--- a/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java
+++ b/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java
@@ -21,6 +21,8 @@ import java.util.List;
 import java.util.UUID;
 import org.apache.commons.io.IOUtils;
 import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.Transaction;
+import org.skife.jdbi.v2.TransactionStatus;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.annotations.AfterClass;
@@ -53,7 +55,7 @@ public class TestTagStore {
     private TagStoreModuleMock module;
     private TagStoreSqlDao tagStoreSqlDao;
     private TagDefinitionDao tagDefinitionDao;
-    private Logger log = LoggerFactory.getLogger(TestTagStore.class);
+    private final Logger log = LoggerFactory.getLogger(TestTagStore.class);
 
     @BeforeClass(alwaysRun = true)
     protected void setup() throws IOException {
@@ -87,6 +89,17 @@ public class TestTagStore {
         module.stopDb();
     }
 
+    private void saveTags(final TagStoreSqlDao dao, final String objectType, final String accountId, final List<Tag> tagList)  {
+        dao.inTransaction(new Transaction<Void, TagStoreSqlDao>() {
+            @Override
+            public Void inTransaction(TagStoreSqlDao transactional,
+                    TransactionStatus status) throws Exception {
+                dao.batchSaveFromTransaction(accountId.toString(), objectType, tagList);
+                return null;
+            }
+        });
+    }
+
     @Test
     public void testTagCreationAndRetrieval() {
         UUID accountId = UUID.randomUUID();
@@ -96,7 +109,7 @@ public class TestTagStore {
         tagStore.add(tag);
 
         TagStoreSqlDao dao = dbi.onDemand(TagStoreSqlDao.class);
-        dao.save(accountId.toString(), ACCOUNT_TYPE, tagStore.getEntityList());
+        saveTags(dao, ACCOUNT_TYPE, accountId.toString(), tagStore.getEntityList());
 
         List<Tag> savedTags = dao.load(accountId.toString(), ACCOUNT_TYPE);
         assertEquals(savedTags.size(), 1);
@@ -108,6 +121,7 @@ public class TestTagStore {
         assertEquals(savedTag.getId(), tag.getId());
     }
 
+
     @Test
     public void testControlTagCreation() {
         UUID accountId = UUID.randomUUID();
@@ -118,7 +132,7 @@ public class TestTagStore {
         assertEquals(tagStore.generateInvoice(), false);
 
         List<Tag> tagList = tagStore.getEntityList();
-        tagStoreSqlDao.save(accountId.toString(), ACCOUNT_TYPE, tagList);
+        saveTags(tagStoreSqlDao, ACCOUNT_TYPE, accountId.toString(), tagList);
 
         tagStore.clear();
         assertEquals(tagStore.getEntityList().size(), 0);
@@ -148,7 +162,7 @@ public class TestTagStore {
         assertEquals(tagStore.generateInvoice(), true);
 
         List<Tag> tagList = tagStore.getEntityList();
-        tagStoreSqlDao.save(accountId.toString(), ACCOUNT_TYPE, tagList);
+        saveTags(tagStoreSqlDao, ACCOUNT_TYPE, accountId.toString(), tagList);
 
         tagStore.clear();
         assertEquals(tagStore.getEntityList().size(), 0);
@@ -182,7 +196,7 @@ public class TestTagStore {
         assertEquals(tagStore.generateInvoice(), false);
 
         List<Tag> tagList = tagStore.getEntityList();
-        tagStoreSqlDao.save(accountId.toString(), ACCOUNT_TYPE, tagList);
+        saveTags(tagStoreSqlDao, ACCOUNT_TYPE, accountId.toString(), tagList);
 
         tagStore.clear();
         assertEquals(tagStore.getEntityList().size(), 0);
@@ -245,7 +259,8 @@ public class TestTagStore {
         Tag tag = new DescriptiveTag(tagDefinition, "test", clock.getUTCNow());
         tagStore.add(tag);
 
-        tagStoreSqlDao.save(objectId.toString(), objectType, tagStore.getEntityList());
+        saveTags(tagStoreSqlDao, objectType, objectId.toString(), tagStore.getEntityList());
+
         List<Tag> tags = tagStoreSqlDao.load(objectId.toString(), objectType);
         assertEquals(tags.size(), 1);
 
@@ -270,7 +285,8 @@ public class TestTagStore {
         Tag tag = new DescriptiveTag(tagDefinition, "test", clock.getUTCNow());
         tagStore.add(tag);
 
-        tagStoreSqlDao.save(objectId.toString(), objectType, tagStore.getEntityList());
+        saveTags(tagStoreSqlDao, objectType, objectId.toString(), tagStore.getEntityList());
+
         List<Tag> tags = tagStoreSqlDao.load(objectId.toString(), objectType);
         assertEquals(tags.size(), 1);