killbill-aplcache

Merge from account branch

12/20/2011 4:32:13 AM

Changes

invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceDao.sql.stg 56(+0 -56)

Details

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 d3588fa..1c7a397 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
@@ -16,11 +16,10 @@
 
 package com.ning.billing.account.api;
 
+import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
-
 import org.joda.time.DateTime;
-
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.util.customfield.CustomizableEntityBase;
 import com.ning.billing.util.tag.DefaultTag;
@@ -39,22 +38,24 @@ public class DefaultAccount extends CustomizableEntityBase implements Account {
     private final Currency currency;
     private final int billCycleDay;
     private final String paymentProviderName;
+    private final BigDecimal balance;
     private final DefaultTagStore tags;
 
     public DefaultAccount(AccountData data) {
         this(UUID.randomUUID(), data.getExternalKey(), data.getEmail(), data.getName(),
                 data.getFirstNameLength(), data.getPhone(), data.getCurrency(), data.getBillCycleDay(),
-                data.getPaymentProviderName());
+                data.getPaymentProviderName(), BigDecimal.ZERO);
     }
 
     public DefaultAccount(UUID id, AccountData data) {
         this(id, data.getExternalKey(), data.getEmail(), data.getName(),
                 data.getFirstNameLength(), data.getPhone(), data.getCurrency(), data.getBillCycleDay(),
-                data.getPaymentProviderName());
+                data.getPaymentProviderName(), BigDecimal.ZERO);
     }
 
     public DefaultAccount(UUID id, String externalKey, String email, String name, int firstNameLength,
-                          String phone, Currency currency, int billCycleDay, String paymentProviderName) {
+                          String phone, Currency currency, int billCycleDay, String paymentProviderName,
+                          BigDecimal balance) {
         super(id);
         this.externalKey = externalKey;
         this.email = email;
@@ -64,6 +65,7 @@ public class DefaultAccount extends CustomizableEntityBase implements Account {
         this.currency = currency;
         this.billCycleDay = billCycleDay;
         this.paymentProviderName = paymentProviderName;
+        this.balance = balance;
 
         this.tags = new DefaultTagStore(id, getObjectName());
     }
@@ -155,8 +157,7 @@ public class DefaultAccount extends CustomizableEntityBase implements Account {
     }
 
     @Override
-    public String toString() {
-        return "DefaultAccount [externalKey=" + externalKey + ", email=" + email + ", name=" + name + ", firstNameLength=" + firstNameLength + ", phone=" + phone + ", currency=" + currency + ", billCycleDay=" + billCycleDay + ", paymentProviderName=" + paymentProviderName + ", tags=" + tags + "]";
+    public BigDecimal getBalance() {
+        return balance;
     }
-
 }
diff --git a/account/src/main/java/com/ning/billing/account/api/user/AccountBuilder.java b/account/src/main/java/com/ning/billing/account/api/user/AccountBuilder.java
index 873b690..e05e967 100644
--- a/account/src/main/java/com/ning/billing/account/api/user/AccountBuilder.java
+++ b/account/src/main/java/com/ning/billing/account/api/user/AccountBuilder.java
@@ -19,6 +19,7 @@ package com.ning.billing.account.api.user;
 import com.ning.billing.account.api.DefaultAccount;
 import com.ning.billing.catalog.api.Currency;
 
+import java.math.BigDecimal;
 import java.util.UUID;
 
 public class AccountBuilder {
@@ -31,6 +32,7 @@ public class AccountBuilder {
     private Currency currency;
     private int billingCycleDay;
     private String paymentProviderName;
+    private BigDecimal balance;
 
     public AccountBuilder() {
         this(UUID.randomUUID());
@@ -80,7 +82,13 @@ public class AccountBuilder {
         return this;
     }
 
+    public AccountBuilder balance(BigDecimal balance) {
+        this.balance = balance;
+        return this;
+    }
+
     public DefaultAccount build() {
-        return new DefaultAccount(id, externalKey, email, name, firstNameLength, phone, currency, billingCycleDay, paymentProviderName);
+        return new DefaultAccount(id, externalKey, email, name, firstNameLength,
+                                  phone, currency, billingCycleDay, paymentProviderName, balance);
     }
 }
diff --git a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountChangeNotification.java b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountChangeNotification.java
index 43ade0f..6bcc634 100644
--- a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountChangeNotification.java
+++ b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountChangeNotification.java
@@ -71,8 +71,17 @@ public class DefaultAccountChangeNotification implements AccountChangeNotificati
             changedFields.add(new DefaultChangedField("billCycleDay", Integer.toString(oldData.getBillCycleDay()),
                                                                Integer.toString(newData.getBillCycleDay())));
         }
-        if (!newData.getPaymentProviderName().equals(oldData.getPaymentProviderName())) {
-            changedFields.add((new DefaultChangedField("paymentProviderName", oldData.getPaymentProviderName(), newData.getPaymentProviderName())));
+
+        String oldProviderName = oldData.getPaymentProviderName();
+        String newProviderName = newData.getPaymentProviderName();
+
+        if ((newProviderName == null) && (oldProviderName == null)) {
+        } else if ((newProviderName == null) && (oldProviderName != null)) {
+            changedFields.add((new DefaultChangedField("paymentProviderName", oldProviderName, newProviderName)));
+        } else if ((newProviderName != null) && (oldProviderName == null)) {
+            changedFields.add((new DefaultChangedField("paymentProviderName", oldProviderName, newProviderName)));
+        } else if (!newProviderName.equals(oldProviderName)) {
+            changedFields.add((new DefaultChangedField("paymentProviderName", oldProviderName, newProviderName)));
         }
 
         return changedFields;
diff --git a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
index 497d139..a20a519 100644
--- a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
+++ b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
@@ -55,6 +55,11 @@ public class DefaultAccountUserApi implements com.ning.billing.account.api.Accou
     }
 
     @Override
+    public UUID getIdFromKey(String externalKey) {
+        return dao.getIdFromKey(externalKey);
+    }
+
+    @Override
     public void saveAccount(Account account) {
         dao.save(account);
     }
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountDao.java b/account/src/main/java/com/ning/billing/account/dao/AccountDao.java
index 52d43ab..04b50e3 100644
--- a/account/src/main/java/com/ning/billing/account/dao/AccountDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountDao.java
@@ -16,9 +16,12 @@
 
 package com.ning.billing.account.dao;
 
+import java.util.UUID;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.util.entity.EntityDao;
 
 public interface AccountDao extends EntityDao<Account> {
     public Account getAccountByKey(String key);
+
+    public UUID getIdFromKey(String externalKey);
 }
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountSqlDao.java b/account/src/main/java/com/ning/billing/account/dao/AccountSqlDao.java
index 4c2a550..40a1358 100644
--- a/account/src/main/java/com/ning/billing/account/dao/AccountSqlDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountSqlDao.java
@@ -17,9 +17,9 @@
 package com.ning.billing.account.dao;
 
 import com.ning.billing.account.api.Account;
-import com.ning.billing.account.api.AccountData;
 import com.ning.billing.account.api.user.AccountBuilder;
 import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.UuidMapper;
 import com.ning.billing.util.entity.EntityDao;
 import org.skife.jdbi.v2.SQLStatement;
 import org.skife.jdbi.v2.StatementContext;
@@ -40,16 +40,20 @@ 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.UUID;
 
 @ExternalizedSqlViaStringTemplate3
-@RegisterMapper(AccountSqlDao.AccountMapper.class)
+@RegisterMapper({UuidMapper.class, AccountSqlDao.AccountMapper.class})
 public interface AccountSqlDao extends EntityDao<Account>, Transactional<AccountSqlDao>, Transmogrifier {
     @SqlQuery
     public Account getAccountByKey(@Bind("externalKey") final String key);
 
+    @SqlQuery
+    public UUID getIdFromKey(@Bind("externalKey") final String key);
+
     @Override
     @SqlUpdate
     public void save(@AccountBinder Account account);
@@ -71,7 +75,8 @@ public interface AccountSqlDao extends EntityDao<Account>, Transactional<Account
                                          .name(name).firstNameLength(firstNameLength)
                                          .phone(phone).currency(currency)
                                          .billingCycleDay(billingCycleDay)
-                                         .paymentProviderName(paymentProviderName).build();
+                                         .paymentProviderName(paymentProviderName)
+                                         .build();
         }
     }
 
@@ -81,8 +86,8 @@ public interface AccountSqlDao extends EntityDao<Account>, Transactional<Account
     public @interface AccountBinder {
         public static class AccountBinderFactory implements BinderFactory {
             public Binder build(Annotation annotation) {
-                return new Binder<AccountBinder, AccountData>() {
-                    public void bind(SQLStatement q, AccountBinder bind, AccountData account) {
+                return new Binder<AccountBinder, Account>() {
+                    public void bind(SQLStatement q, AccountBinder bind, Account account) {
                         q.bind("id", account.getId().toString());
                         q.bind("externalKey", account.getExternalKey());
                         q.bind("email", account.getEmail());
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 3df35c9..c108bb4 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
@@ -17,6 +17,7 @@
 package com.ning.billing.account.dao;
 
 import java.util.List;
+import java.util.UUID;
 import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.TransactionStatus;
@@ -28,7 +29,6 @@ import com.ning.billing.account.api.DefaultAccount;
 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.FieldStore;
 import com.ning.billing.util.customfield.dao.FieldStoreDao;
 import com.ning.billing.util.eventbus.EventBus;
 import com.ning.billing.util.tag.Tag;
@@ -55,10 +55,10 @@ public class DefaultAccountDao implements AccountDao {
                     FieldStoreDao fieldStoreDao = accountSqlDao.become(FieldStoreDao.class);
                     List<CustomField> fields = fieldStoreDao.load(account.getId().toString(), account.getObjectName());
 
-                    account.getFields().clear();
+                    account.clearFields();
                     if (fields != null) {
                         for (CustomField field : fields) {
-                            account.getFields().setValue(field.getName(), field.getValue());
+                            account.setFieldValue(field.getName(), field.getValue());
                         }
                     }
 
@@ -77,6 +77,11 @@ public class DefaultAccountDao implements AccountDao {
     }
 
     @Override
+    public UUID getIdFromKey(final String externalKey) {
+        return accountDao.getIdFromKey(externalKey);
+    }
+
+    @Override
     public Account getById(final String id) {
         return accountDao.inTransaction(new Transaction<Account, AccountSqlDao>() {
             @Override
@@ -87,10 +92,10 @@ public class DefaultAccountDao implements AccountDao {
                     FieldStoreDao fieldStoreDao = accountSqlDao.become(FieldStoreDao.class);
                     List<CustomField> fields = fieldStoreDao.load(account.getId().toString(), account.getObjectName());
 
-                    account.getFields().clear();
+                    account.clearFields();
                     if (fields != null) {
                         for (CustomField field : fields) {
-                            account.getFields().setValue(field.getName(), field.getValue());
+                            account.setFieldValue(field.getName(), field.getValue());
                         }
                     }
 
@@ -129,9 +134,8 @@ public class DefaultAccountDao implements AccountDao {
                 Account currentAccount = accountDao.getById(accountId);
                 accountDao.save(account);
 
-                FieldStore fieldStore = account.getFields();
                 FieldStoreDao fieldStoreDao = accountDao.become(FieldStoreDao.class);
-                fieldStoreDao.save(accountId, objectType, fieldStore.getEntityList());
+                fieldStoreDao.save(accountId, objectType, account.getFieldList());
 
                 TagStoreDao tagStoreDao = fieldStoreDao.become(TagStoreDao.class);
                 tagStoreDao.save(accountId, objectType, account.getTagList());
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 d70548d..275d4c9 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
@@ -32,8 +32,6 @@ public class AccountModule extends AbstractModule {
     }
 
     private void installAccountCore() {
-//        bind(IAccountService.class).to(Engine.class).asEagerSingleton();
-//        bind(Engine.class).asEagerSingleton();
     }
 
     private void installAccountDao() {
diff --git a/account/src/main/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg b/account/src/main/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg
index 33d60b8..3e0ebdd 100644
--- a/account/src/main/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg
+++ b/account/src/main/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg
@@ -17,9 +17,11 @@ getAccountByKey() ::= <<
 >>
 
 getById() ::= <<
-    select id, external_key, email, name, first_name_length, phone, currency, billing_cycle_day, payment_provider_name
-    from accounts
-    where id = :id;
+    select
+      a.id, a.external_key, a.email, a.name, a.first_name_length,
+      a.phone, a.currency, a.billing_cycle_day, a.payment_provider_name
+    from accounts a
+    where a.id = :id;
 >>
 
 get() ::= <<
@@ -27,6 +29,12 @@ get() ::= <<
     from accounts;
 >>
 
+getIdFromKey() ::= <<
+    select id
+    from accounts
+    where external_key = :externalKey;
+>>
+
 test() ::= <<
     select 1 from accounts;
 >>
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 ce9570f..77efe00 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
@@ -24,6 +24,7 @@ import com.ning.billing.util.eventbus.DefaultEventBusService;
 import com.ning.billing.util.eventbus.EventBusService;
 import org.apache.commons.io.IOUtils;
 import org.skife.jdbi.v2.IDBI;
+import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 
 import java.io.IOException;
@@ -31,7 +32,7 @@ import java.io.IOException;
 import static org.testng.Assert.fail;
 
 public abstract class AccountDaoTestBase {
-    //protected FieldStoreDao fieldStoreDao;
+    protected AccountModuleMock module;
     protected AccountDao accountDao;
     protected IDBI dbi;
 
@@ -39,15 +40,14 @@ public abstract class AccountDaoTestBase {
     protected void setup() throws IOException {
         // Healthcheck test to make sure MySQL is setup properly
         try {
-            AccountModuleMock module = new AccountModuleMock();
-            final String ddl = IOUtils.toString(AccountSqlDao.class.getResourceAsStream("/com/ning/billing/account/ddl.sql"));
-            module.createDb(ddl);
+            module = new AccountModuleMock();
+            final String accountDdl = IOUtils.toString(AccountSqlDao.class.getResourceAsStream("/com/ning/billing/account/ddl.sql"));
+            final String invoiceDdl = IOUtils.toString(AccountSqlDao.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
+            module.startDb();
+            module.initDb(accountDdl);
+            module.initDb(invoiceDdl);
 
             final Injector injector = Guice.createInjector(Stage.DEVELOPMENT, module);
-
-            //fieldStoreDao = injector.getInstance(FieldStoreDao.class);
-            //fieldStoreDao.test();
-
             dbi = injector.getInstance(IDBI.class);
 
             accountDao = injector.getInstance(AccountDao.class);
@@ -60,4 +60,10 @@ public abstract class AccountDaoTestBase {
             fail(t.toString());
         }
     }
+
+    @AfterClass(alwaysRun = true)
+    public void stopMysql()
+    {
+        module.stopDb();
+    }
 }
diff --git a/account/src/test/java/com/ning/billing/account/dao/TestSimpleAccountDao.java b/account/src/test/java/com/ning/billing/account/dao/TestSimpleAccountDao.java
index 0deec50..7a82f98 100644
--- a/account/src/test/java/com/ning/billing/account/dao/TestSimpleAccountDao.java
+++ b/account/src/test/java/com/ning/billing/account/dao/TestSimpleAccountDao.java
@@ -21,6 +21,7 @@ import java.util.UUID;
 import org.joda.time.DateTime;
 import org.testng.annotations.Test;
 import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountData;
 import com.ning.billing.account.api.DefaultAccount;
 import com.ning.billing.util.tag.DefaultTagDescription;
 import com.ning.billing.util.tag.Tag;
@@ -46,9 +47,10 @@ public class TestSimpleAccountDao extends AccountDaoTestBase {
         String lastName = UUID.randomUUID().toString();
         String thisEmail = email + " " + UUID.randomUUID();
         String name = firstName + " " + lastName;
+        String phone = "123-456-7890";
 
         int firstNameLength = firstName.length();
-        return new AccountBuilder().externalKey(thisKey).name(name).firstNameLength(firstNameLength)
+        return new AccountBuilder().externalKey(thisKey).name(name).phone(phone).firstNameLength(firstNameLength)
                                    .email(thisEmail).currency(Currency.USD).build();
     }
 
@@ -129,4 +131,68 @@ public class TestSimpleAccountDao extends AccountDaoTestBase {
         assertEquals(tag.getAddedBy(), addedBy);
         assertEquals(tag.getDateAdded().compareTo(dateAdded), 0);
     }
+
+    @Test
+    public void testGetIdFromKey() {
+        Account account = createTestAccount();
+        accountDao.save(account);
+
+        UUID accountId = accountDao.getIdFromKey(account.getExternalKey());
+        assertEquals(accountId, account.getId());
+    }
+
+    @Test
+    public void testUpdate() {
+        final Account account = createTestAccount();
+        accountDao.save(account);
+
+        AccountData accountData = new AccountData() {
+            @Override
+            public String getExternalKey() {
+                return account.getExternalKey();
+            }
+            @Override
+            public String getName() {
+                return "Jane Doe";
+            }
+            @Override
+            public int getFirstNameLength() {
+                return 4;
+            }
+            @Override
+            public String getEmail() {
+                return account.getEmail();
+            }
+            @Override
+            public String getPhone() {
+                return account.getPhone();
+            }
+            @Override
+            public int getBillCycleDay() {
+                return account.getBillCycleDay();
+            }
+            @Override
+            public Currency getCurrency() {
+                return account.getCurrency();
+            }
+            @Override
+            public String getPaymentProviderName() {
+                return account.getPaymentProviderName();
+            }
+        };
+
+        Account updatedAccount = new DefaultAccount(account.getId(), accountData);
+        accountDao.save(updatedAccount);
+
+        Account savedAccount = accountDao.getAccountByKey(account.getExternalKey());
+
+        assertNotNull(savedAccount);
+        assertEquals(savedAccount.getName(), updatedAccount.getName());
+        assertEquals(savedAccount.getEmail(), updatedAccount.getEmail());
+        assertEquals(savedAccount.getPhone(), updatedAccount.getPhone());
+        assertEquals(savedAccount.getPaymentProviderName(), updatedAccount.getPaymentProviderName());
+        assertEquals(savedAccount.getBillCycleDay(), updatedAccount.getBillCycleDay());
+        assertEquals(savedAccount.getFirstNameLength(), updatedAccount.getFirstNameLength());
+
+    }
 }
diff --git a/account/src/test/java/com/ning/billing/account/glue/AccountModuleMock.java b/account/src/test/java/com/ning/billing/account/glue/AccountModuleMock.java
index 54a761e..1123059 100644
--- a/account/src/test/java/com/ning/billing/account/glue/AccountModuleMock.java
+++ b/account/src/test/java/com/ning/billing/account/glue/AccountModuleMock.java
@@ -25,11 +25,18 @@ import java.io.IOException;
 public class AccountModuleMock extends AccountModule {
     private final MysqlTestingHelper helper = new MysqlTestingHelper();
 
-    public void createDb(String ddl) throws IOException {
+    public void startDb() throws IOException {
         helper.startMysql();
+    }
+
+    public void initDb(String ddl) throws IOException {
         helper.initDb(ddl);
     }
 
+    public void stopDb() {
+        helper.stopMysql();
+    }
+
     @Override
     protected void configure() {
         bind(IDBI.class).toInstance(helper.getDBI());
diff --git a/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java b/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
index ba7a707..cce74e7 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
@@ -18,6 +18,8 @@ package com.ning.billing.analytics;
 
 import com.google.common.eventbus.Subscribe;
 import com.google.inject.Inject;
+import com.ning.billing.account.api.AccountChangeNotification;
+import com.ning.billing.account.api.AccountCreationNotification;
 import com.ning.billing.entitlement.api.user.SubscriptionTransition;
 
 public class AnalyticsListener
@@ -62,7 +64,18 @@ public class AnalyticsListener
     }
 
     @Subscribe
-    public void handleAccountChange(final Object event)
+    public void handleAccountCreation(final AccountCreationNotification event)
     {
+        bacRecorder.accountCreated(event.getData());
+    }
+
+    @Subscribe
+    public void handleAccountChange(final AccountChangeNotification event)
+    {
+        if (!event.hasChanges()) {
+            return;
+        }
+
+        bacRecorder.accountUpdated(event.getAccountId(), event.getChangedFields());
     }
 }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessAccountRecorder.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessAccountRecorder.java
index 185289a..23fbcf4 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessAccountRecorder.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessAccountRecorder.java
@@ -18,11 +18,18 @@ package com.ning.billing.analytics;
 
 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.AccountUserApi;
+import com.ning.billing.account.api.ChangedField;
 import com.ning.billing.analytics.dao.BusinessAccountDao;
+import com.ning.billing.util.tag.Tag;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
 public class BusinessAccountRecorder
 {
     private static final Logger log = LoggerFactory.getLogger(BusinessAccountRecorder.class);
@@ -37,11 +44,35 @@ public class BusinessAccountRecorder
         this.accountApi = accountApi;
     }
 
-    public void subscriptionCreated(final Account created)
+    public void accountCreated(final AccountData data)
     {
+        final Account account = accountApi.getAccountByKey(data.getExternalKey());
+
+        final List<String> tags = new ArrayList<String>();
+        for (final Tag tag : account.getTagList()) {
+            tags.add(tag.getName());
+        }
+
+        // TODO Need payment and invoice api to fill most fields
+        final BusinessAccount bac = new BusinessAccount(
+            account.getExternalKey(),
+            null, // TODO
+            tags,
+            null, // TODO
+            null, // TODO
+            null, // TODO
+            null, // TODO
+            null, // TODO
+            null // TODO
+        );
+
+        log.info("ACCOUNT CREATION " + bac);
+        dao.createAccount(bac);
     }
 
-    public void subscriptionUpdated(final Account updated)
+    public void accountUpdated(final UUID accountId, final List<ChangedField> changedFields)
     {
+        // None of the fields updated interest us so far - see DefaultAccountChangeNotification
+        // TODO We'll need notifications for tags changes eventually
     }
 }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountBinder.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountBinder.java
index 6a3e218..6478536 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountBinder.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountBinder.java
@@ -30,6 +30,7 @@ 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(BusinessAccountBinder.BacBinderFactory.class)
 @Retention(RetentionPolicy.RUNTIME)
@@ -59,7 +60,12 @@ public @interface BusinessAccountBinder
                     q.bind("account_key", account.getKey());
                     q.bind("balance", account.getRoundedBalance());
                     q.bind("tags", joiner.join(account.getTags()));
-                    q.bind("last_invoice_date", account.getLastInvoiceDate().getMillis());
+                    if (account.getLastInvoiceDate() != null) {
+                        q.bind("last_invoice_date", account.getLastInvoiceDate().getMillis());
+                    }
+                    else {
+                        q.bindNull("last_invoice_date", Types.BIGINT);
+                    }
                     q.bind("total_invoice_balance", account.getRoundedTotalInvoiceBalance());
                     q.bind("last_payment_status", account.getLastPaymentStatus());
                     q.bind("payment_method", account.getPaymentMethod());
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 03e102d..3c3de7e 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
@@ -22,6 +22,7 @@ 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.EventBusModule;
+import com.ning.billing.util.glue.TagStoreModule;
 import org.skife.jdbi.v2.DBI;
 import org.skife.jdbi.v2.IDBI;
 
@@ -37,6 +38,7 @@ public class AnalyticsTestModule extends AnalyticsModule
         install(new CatalogModule());
         install(new EventBusModule());
         install(new EntitlementModule());
+        install(new TagStoreModule());
 
         // Install the Dao layer
         final MysqlTestingHelper helper = new MysqlTestingHelper();
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 b192698..03a27d0 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
@@ -18,15 +18,38 @@ package com.ning.billing.analytics.api;
 
 import com.google.inject.Inject;
 import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountCreationNotification;
 import com.ning.billing.account.api.AccountUserApi;
-import com.ning.billing.analytics.*;
+import com.ning.billing.account.api.user.DefaultAccountCreationEvent;
+import com.ning.billing.analytics.AnalyticsTestModule;
+import com.ning.billing.analytics.BusinessSubscription;
+import com.ning.billing.analytics.BusinessSubscriptionEvent;
+import com.ning.billing.analytics.BusinessSubscriptionTransition;
+import com.ning.billing.analytics.MockAccount;
+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.dao.BusinessAccountDao;
 import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionDao;
-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.*;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+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.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.tag.DefaultTagDescription;
+import com.ning.billing.util.tag.dao.TagDescriptionDao;
 import org.apache.commons.io.IOUtils;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
@@ -45,6 +68,8 @@ public class TestAnalyticsService
 {
     private static final String KEY = "1234";
     private static final String ACCOUNT_KEY = "pierre-1234";
+    private static final DefaultTagDescription TAG_ONE = new DefaultTagDescription("batch20", "something", false, false, "pierre", new DateTime(DateTimeZone.UTC));
+    private static final DefaultTagDescription TAG_TWO = new DefaultTagDescription("awesome", "something", false, false, "pierre", new DateTime(DateTimeZone.UTC));
 
     @Inject
     private AccountUserApi accountApi;
@@ -53,13 +78,19 @@ public class TestAnalyticsService
     private EntitlementUserApi entitlementApi;
 
     @Inject
+    private TagDescriptionDao tagDao;
+
+    @Inject
     private AnalyticsService service;
 
     @Inject
     private EventBus bus;
 
     @Inject
-    private BusinessSubscriptionTransitionDao dao;
+    private BusinessSubscriptionTransitionDao subscriptionDao;
+
+    @Inject
+    private BusinessAccountDao accountDao;
 
     @Inject
     private MysqlTestingHelper helper;
@@ -67,13 +98,33 @@ public class TestAnalyticsService
     private SubscriptionTransition transition;
     private BusinessSubscriptionTransition expectedTransition;
 
+    private AccountCreationNotification accountCreationNotification;
+
     @BeforeClass(alwaysRun = true)
     public void startMysql() throws IOException, ClassNotFoundException, SQLException, EntitlementUserApiException
     {
+        // Killbill generic setup
+        setupBusAndMySQL();
+
+        tagDao.save(TAG_ONE);
+        tagDao.save(TAG_TWO);
+
+        final MockAccount account = new MockAccount(UUID.randomUUID(), ACCOUNT_KEY, Currency.USD);
+        final Account storedAccount = accountApi.createAccount(account);
+        storedAccount.addTag(TAG_ONE, "pierre", new DateTime(DateTimeZone.UTC));
+        storedAccount.addTag(TAG_TWO, "pierre", new DateTime(DateTimeZone.UTC));
+        accountApi.saveAccount(storedAccount);
+
+        // Create events for the bus and expected results
+        createSubscriptionTransitionEvent(storedAccount);
+        createAccountCreationEvent(storedAccount);
+    }
+
+    private void setupBusAndMySQL() throws IOException
+    {
         bus.start();
 
         final String analyticsDdl = IOUtils.toString(BusinessSubscriptionTransitionDao.class.getResourceAsStream("/com/ning/billing/analytics/ddl.sql"));
-        // For bundles
         final String accountDdl = IOUtils.toString(BusinessSubscriptionTransitionDao.class.getResourceAsStream("/com/ning/billing/account/ddl.sql"));
         final String entitlementDdl = IOUtils.toString(BusinessSubscriptionTransitionDao.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
 
@@ -81,17 +132,17 @@ public class TestAnalyticsService
         helper.initDb(analyticsDdl);
         helper.initDb(accountDdl);
         helper.initDb(entitlementDdl);
+    }
 
-        // We need a bundle to retrieve the event key
-        final MockAccount account = new MockAccount(UUID.randomUUID(), ACCOUNT_KEY, Currency.USD);
-        final Account storedAccount = accountApi.createAccount(account);
-        final SubscriptionBundle bundle = entitlementApi.createBundleForAccount(storedAccount, KEY);
+    private void createSubscriptionTransitionEvent(final Account account) throws EntitlementUserApiException
+    {
+        final SubscriptionBundle bundle = entitlementApi.createBundleForAccount(account.getId(), KEY);
 
         // Verify we correctly initialized the account subsystem
         Assert.assertNotNull(bundle);
         Assert.assertEquals(bundle.getKey(), KEY);
 
-        // Create a subscription transition
+        // Create a subscription transition event
         final Product product = new MockProduct("platinum", "subscription", ProductCategory.BASE);
         final Plan plan = new MockPlan("platinum-monthly", product);
         final PlanPhase phase = new MockPhase(PhaseType.EVERGREEN, plan, MockDuration.UNLIMITED(), 25.95);
@@ -99,6 +150,7 @@ public class TestAnalyticsService
         final DateTime effectiveTransitionTime = new DateTime(DateTimeZone.UTC);
         final DateTime requestedTransitionTime = new DateTime(DateTimeZone.UTC);
         final String priceList = "something";
+
         transition = new SubscriptionTransitionData(
             UUID.randomUUID(),
             subscriptionId,
@@ -126,6 +178,11 @@ public class TestAnalyticsService
         );
     }
 
+    private void createAccountCreationEvent(final Account account)
+    {
+        accountCreationNotification = new DefaultAccountCreationEvent(account);
+    }
+
     @AfterClass(alwaysRun = true)
     public void stopMysql()
     {
@@ -146,11 +203,18 @@ public class TestAnalyticsService
             Assert.fail("Unable to start the bus or service! " + t);
         }
 
-        // Send an event to the bus and make sure our Dao got it
+        // Send events and wait for the async part...
         bus.post(transition);
+        bus.post(accountCreationNotification);
         Thread.sleep(1000);
-        Assert.assertEquals(dao.getTransitions(KEY).size(), 1);
-        Assert.assertEquals(dao.getTransitions(KEY).get(0), expectedTransition);
+
+        Assert.assertEquals(subscriptionDao.getTransitions(KEY).size(), 1);
+        Assert.assertEquals(subscriptionDao.getTransitions(KEY).get(0), expectedTransition);
+
+        Assert.assertEquals(accountDao.getAccount(ACCOUNT_KEY).getKey(), ACCOUNT_KEY);
+        Assert.assertEquals(accountDao.getAccount(ACCOUNT_KEY).getTags().size(), 2);
+        Assert.assertTrue(accountDao.getAccount(ACCOUNT_KEY).getTags().indexOf(TAG_ONE.getName()) != -1);
+        Assert.assertTrue(accountDao.getAccount(ACCOUNT_KEY).getTags().indexOf(TAG_TWO.getName()) != -1);
 
         // Test the shutdown sequence
         try {
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java b/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java
index dbb7f8a..faf4dc8 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java
@@ -16,11 +16,20 @@
 
 package com.ning.billing.analytics;
 
+import sun.reflect.generics.reflectiveObjects.NotImplementedException;
+
+import java.math.BigDecimal;
+import java.util.List;
 import java.util.UUID;
+import org.joda.time.DateTime;
+import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountData;
 import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDescription;
 
-public class MockAccount implements AccountData
+public class MockAccount implements Account
 {
     private final UUID id;
     private final String accountKey;
@@ -83,4 +92,74 @@ public class MockAccount implements AccountData
     {
         return id;
     }
+
+    @Override
+    public String getFieldValue(String fieldName) {
+        throw new NotImplementedException();
+    }
+
+    @Override
+    public void setFieldValue(String fieldName, String fieldValue) {
+        throw new NotImplementedException();
+    }
+
+    @Override
+    public List<CustomField> getFieldList() {
+        throw new NotImplementedException();
+    }
+
+    @Override
+    public void clearFields() {
+        throw new NotImplementedException();
+    }
+
+    @Override
+    public String getObjectName() {
+        throw new NotImplementedException();
+    }
+
+    @Override
+    public List<Tag> getTagList() {
+        throw new NotImplementedException();
+    }
+
+    @Override
+    public boolean hasTag(String tagName) {
+        throw new NotImplementedException();
+    }
+
+    @Override
+    public void addTag(TagDescription description, String addedBy, DateTime dateAdded) {
+        throw new NotImplementedException();
+    }
+
+    @Override
+    public void addTags(List<Tag> tags) {
+        throw new NotImplementedException();
+    }
+
+    @Override
+    public void clearTags() {
+        throw new NotImplementedException();
+    }
+
+    @Override
+    public void removeTag(TagDescription description) {
+        throw new NotImplementedException();
+    }
+
+    @Override
+    public boolean generateInvoice() {
+        throw new NotImplementedException();
+    }
+
+    @Override
+    public boolean processPayment() {
+        throw new NotImplementedException();
+    }
+
+    @Override
+    public BigDecimal getBalance() {
+        return BigDecimal.ZERO;
+    }
 }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockIAccountUserApi.java b/analytics/src/test/java/com/ning/billing/analytics/MockIAccountUserApi.java
index 4982fe2..4c39456 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockIAccountUserApi.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockIAccountUserApi.java
@@ -29,10 +29,12 @@ import com.ning.billing.catalog.api.Currency;
 public class MockIAccountUserApi implements AccountUserApi
 {
     private final AccountData account;
+    private final UUID id;
 
     public MockIAccountUserApi(final String accountKey, final Currency currency)
     {
-        account = new MockAccount(UUID.randomUUID(), accountKey, currency);
+        this.id = UUID.randomUUID();
+        account = new MockAccount(id, accountKey, currency);
     }
 
     @Override
@@ -63,4 +65,9 @@ public class MockIAccountUserApi implements AccountUserApi
     {
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public UUID getIdFromKey(String externalKey) {
+        return id;
+    }
 }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockIEntitlementUserApi.java b/analytics/src/test/java/com/ning/billing/analytics/MockIEntitlementUserApi.java
index 47ce151..3017413 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockIEntitlementUserApi.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockIEntitlementUserApi.java
@@ -93,7 +93,7 @@ public class MockIEntitlementUserApi implements EntitlementUserApi
     }
 
     @Override
-    public SubscriptionBundle createBundleForAccount(final AccountData account, final String bundleKey) throws EntitlementUserApiException
+    public SubscriptionBundle createBundleForAccount(final UUID accountId, final String bundleKey) throws EntitlementUserApiException
     {
         throw new UnsupportedOperationException();
     }
diff --git a/api/src/main/java/com/ning/billing/account/api/Account.java b/api/src/main/java/com/ning/billing/account/api/Account.java
index 1cc9aa9..543f280 100644
--- a/api/src/main/java/com/ning/billing/account/api/Account.java
+++ b/api/src/main/java/com/ning/billing/account/api/Account.java
@@ -16,9 +16,10 @@
 
 package com.ning.billing.account.api;
 
+import java.math.BigDecimal;
 import com.ning.billing.util.customfield.CustomizableEntity;
 import com.ning.billing.util.tag.Taggable;
 
 public interface Account extends AccountData, CustomizableEntity, Taggable {
-
+    public BigDecimal getBalance();
 }
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountData.java b/api/src/main/java/com/ning/billing/account/api/AccountData.java
index 87d71c1..9b8f399 100644
--- a/api/src/main/java/com/ning/billing/account/api/AccountData.java
+++ b/api/src/main/java/com/ning/billing/account/api/AccountData.java
@@ -19,7 +19,7 @@ package com.ning.billing.account.api;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.util.entity.Entity;
 
-public interface AccountData extends Entity {
+public interface AccountData {
 
     public String getExternalKey();
 
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 879f642..b34d9a3 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
@@ -27,7 +27,9 @@ public interface AccountUserApi {
 
     public Account getAccountByKey(String key);
 
-    public Account getAccountById(UUID uid);
+    public Account getAccountById(UUID accountId);
 
     public List<Account> getAccounts();
+
+    public UUID getIdFromKey(String externalKey);
 }
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApi.java b/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApi.java
index 10f8591..1f431ed 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApi.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApi.java
@@ -36,10 +36,9 @@ public interface EntitlementUserApi {
 
     public List<Subscription> getSubscriptionsForKey(String bundleKey);
 
-    public SubscriptionBundle createBundleForAccount(AccountData account, String bundleKey)
+    public SubscriptionBundle createBundleForAccount(UUID accountId, String bundleKey)
         throws EntitlementUserApiException;
 
-
     public Subscription createSubscription(UUID bundleId, String productName, BillingPeriod term, String priceList, PhaseType initialPhase, DateTime requestedDate)
         throws EntitlementUserApiException;
 }
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 6e9f57f..bde12a9 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
@@ -21,9 +21,12 @@ import com.ning.billing.catalog.api.Currency;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
+import org.joda.time.DateTime;
 
 public interface InvoicePaymentApi {
-    public void paymentSuccessful(UUID invoiceId, BigDecimal amount, Currency currency, UUID paymentId);
+    public void paymentSuccessful(UUID invoiceId, BigDecimal amount, Currency currency, UUID paymentId, DateTime paymentAttemptDate);
+
+    public void paymentFailed(UUID invoiceId, UUID paymentId, DateTime paymentAttemptDate);
 
     public List<Invoice> getInvoicesByAccount(UUID accountId);
 
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 a32ba01..2c8d02e 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
@@ -21,6 +21,7 @@ import org.joda.time.DateTime;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
+import com.ning.billing.catalog.api.Currency;
 
 public interface InvoiceUserApi {
     public List<UUID> getInvoicesForPayment(DateTime targetDate, int numberOfDays);
@@ -29,7 +30,8 @@ public interface InvoiceUserApi {
 
     public Invoice getInvoice(UUID invoiceId);
 
-    public void paymentAttemptFailed(UUID invoiceId, DateTime paymentAttemptDate);
+    public void paymentAttemptFailed(UUID invoiceId, UUID paymentId, DateTime paymentAttemptDate);
 
-    public void paymentAttemptSuccessful(UUID invoiceId, DateTime paymentAttemptDate, BigDecimal paymentAmount);
+    public void paymentAttemptSuccessful(UUID invoiceId, BigDecimal amount, Currency currency,
+                                         UUID paymentId, DateTime paymentDate);
 }
diff --git a/api/src/main/java/com/ning/billing/util/customfield/CustomizableEntity.java b/api/src/main/java/com/ning/billing/util/customfield/CustomizableEntity.java
index 6ed8192..7b8f37a 100644
--- a/api/src/main/java/com/ning/billing/util/customfield/CustomizableEntity.java
+++ b/api/src/main/java/com/ning/billing/util/customfield/CustomizableEntity.java
@@ -16,6 +16,7 @@
 
 package com.ning.billing.util.customfield;
 
+import java.util.List;
 import com.ning.billing.util.entity.Entity;
 
 public interface CustomizableEntity extends Entity {
@@ -23,7 +24,9 @@ public interface CustomizableEntity extends Entity {
 
     public void setFieldValue(String fieldName, String fieldValue);
 
-    public FieldStore getFields();
+    public List<CustomField> getFieldList();
+
+    public void clearFields();
 
     public String getObjectName();
 }
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 135ce3e..2cf23bc 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
@@ -78,9 +78,9 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
     }
 
     @Override
-    public SubscriptionBundle createBundleForAccount(AccountData account, String bundleName)
+    public SubscriptionBundle createBundleForAccount(UUID accountId, String bundleName)
     throws EntitlementUserApiException {
-        SubscriptionBundleData bundle = new SubscriptionBundleData(bundleName, account.getId());
+        SubscriptionBundleData bundle = new SubscriptionBundleData(bundleName, accountId);
         return dao.createSubscriptionBundle(bundle);
     }
 
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 3838845..e676af4 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
@@ -71,9 +71,14 @@ public class EntitlementSqlDao implements EntitlementDao {
     }
 
     @Override
-    public SubscriptionBundle createSubscriptionBundle(SubscriptionBundleData bundle) {
-        bundlesDao.insertBundle(bundle);
-        return bundle;
+    public SubscriptionBundle createSubscriptionBundle(final SubscriptionBundleData bundle) {
+        return bundlesDao.inTransaction(new Transaction<SubscriptionBundle, BundleSqlDao>() {
+            @Override
+            public SubscriptionBundle inTransaction(BundleSqlDao bundlesDao, TransactionStatus status) {
+                bundlesDao.insertBundle(bundle);
+                return bundle;
+            }
+        });
     }
 
     @Override
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiBase.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiBase.java
index 72d60aa..c886ff5 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiBase.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiBase.java
@@ -158,7 +158,8 @@ public abstract class TestUserApiBase {
         ((MockEntitlementDao) dao).reset();
         try {
             busService.getEventBus().register(testListener);
-            bundle = entitlementApi.createBundleForAccount(accountData, "myDefaultBundle");
+            UUID accountId = UUID.randomUUID();
+            bundle = entitlementApi.createBundleForAccount(accountId, "myDefaultBundle");
         } catch (Exception e) {
             Assert.fail(e.getMessage());
         }
@@ -283,8 +284,6 @@ public abstract class TestUserApiBase {
 
     protected AccountData getAccountData() {
         AccountData accountData = new AccountData() {
-            private final UUID id = UUID.randomUUID();
-
             @Override
             public String getName() {
                 return "firstName lastName";
@@ -319,10 +318,6 @@ public abstract class TestUserApiBase {
             public String getPaymentProviderName() {
                 return "Paypal";
             }
-            @Override
-            public UUID getId() {
-                return id;
-            }
         };
         return accountData;
     }
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 572424c..22f4990 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
@@ -17,9 +17,12 @@
 package com.ning.billing.invoice.api.user;
 
 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.InvoiceUserApi;
+import com.ning.billing.invoice.dao.DefaultInvoiceDao;
 import com.ning.billing.invoice.dao.InvoiceDao;
+import com.ning.billing.util.eventbus.EventBus;
 import org.joda.time.DateTime;
 import org.skife.jdbi.v2.IDBI;
 
@@ -31,8 +34,8 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
     private final InvoiceDao dao;
 
     @Inject
-    public DefaultInvoiceUserApi(IDBI dbi) {
-        dao = dbi.onDemand(InvoiceDao.class);
+    public DefaultInvoiceUserApi(InvoiceDao dao) {
+        this.dao = dao;
     }
 
     @Override
@@ -51,12 +54,12 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
     }
 
     @Override
-    public void paymentAttemptFailed(UUID invoiceId, DateTime paymentAttemptDate) {
-        dao.notifyFailedPayment(invoiceId.toString(), paymentAttemptDate.toDate());
+    public void paymentAttemptFailed(UUID invoiceId, UUID paymentId, DateTime paymentAttemptDate) {
+        dao.notifyFailedPayment(invoiceId.toString(), paymentId.toString(), paymentAttemptDate.toDate());
     }
 
     @Override
-    public void paymentAttemptSuccessful(UUID invoiceId, DateTime paymentAttemptDate, BigDecimal paymentAmount) {
-        dao.notifySuccessfulPayment(invoiceId.toString(), paymentAttemptDate.toDate(), paymentAmount);
+    public void paymentAttemptSuccessful(UUID invoiceId, BigDecimal amount, Currency currency, UUID paymentId, DateTime paymentDate) {
+        dao.notifySuccessfulPayment(invoiceId.toString(), amount, currency.toString(), paymentId.toString(), paymentDate.toDate());
     }
 }
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 9beb139..477be07 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,91 +16,33 @@
 
 package com.ning.billing.invoice.dao;
 
-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.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.customizers.RegisterMapper;
-import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
-import org.skife.jdbi.v2.tweak.ResultSetMapper;
-
-import java.lang.annotation.*;
 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.invoice.api.Invoice;
 
-@ExternalizedSqlViaStringTemplate3()
-@RegisterMapper({UuidMapper.class, InvoiceDao.InvoiceMapper.class})
-public interface InvoiceDao extends EntityDao<Invoice> {
-    @Override
-    @SqlUpdate
-    void save(@InvoiceBinder Invoice invoice);
-
-    @SqlQuery
-    List<Invoice> getInvoicesByAccount(@Bind("accountId") final String accountId);
+public interface InvoiceDao {
+    void save(Invoice invoice);
 
-    @SqlQuery
-    List<Invoice> getInvoicesBySubscription(@Bind("subscriptionId") final String subscriptionId);
+    Invoice getById(final String id);
 
-    @SqlQuery
-    List<UUID> getInvoicesForPayment(@Bind("targetDate") final Date targetDate,
-                                     @Bind("numberOfDays") final int numberOfDays);
+    List<Invoice> getInvoicesByAccount(final String accountId);
 
-    @SqlUpdate
-    void notifySuccessfulPayment(@Bind("id") final String invoiceId,
-                                 @Bind("paymentDate") final Date paymentDate,
-                                 @Bind("paymentAmount") final BigDecimal paymentAmount);
+    List<Invoice> getInvoicesBySubscription(final String subscriptionId);
 
-    @SqlUpdate
-    void notifyFailedPayment(@Bind("id") final String invoiceId,
-                             @Bind("paymentAttemptDate") final Date paymentAttemptDate);
+    List<UUID> getInvoicesForPayment(final Date targetDate,
+                                     final int numberOfDays);
 
-    @BindingAnnotation(InvoiceBinder.InvoiceBinderFactory.class)
-    @Retention(RetentionPolicy.RUNTIME)
-    @Target({ElementType.PARAMETER})
-    public @interface InvoiceBinder {
-        public static class InvoiceBinderFactory implements BinderFactory {
-            public Binder build(Annotation annotation) {
-                return new Binder<InvoiceBinder, Invoice>() {
-                    public void bind(SQLStatement q, InvoiceBinder bind, Invoice invoice) {
-                        q.bind("id", invoice.getId().toString());
-                        q.bind("accountId", invoice.getAccountId().toString());
-                        q.bind("invoiceDate", invoice.getInvoiceDate().toDate());
-                        q.bind("targetDate", invoice.getTargetDate().toDate());
-                        q.bind("amountPaid", invoice.getAmountPaid());
-                        q.bind("amountOutstanding", invoice.getAmountOutstanding());
-                        DateTime invoiceDate = invoice.getLastPaymentAttempt();
-                        q.bind("lastPaymentAttempt", invoiceDate == null ? null : invoiceDate.toDate());
-                        q.bind("currency", invoice.getCurrency().toString());
-                    }
-                };
-            }
-        }
-    }
+    void notifySuccessfulPayment(final String invoiceId,
+                                 final BigDecimal paymentAmount,
+                                 final String currency,
+                                 final String paymentId,
+                                 final Date paymentDate);
 
-    public static class InvoiceMapper implements ResultSetMapper<Invoice> {
-        @Override
-        public Invoice map(int index, ResultSet result, StatementContext context) throws SQLException {
-            UUID id = UUID.fromString(result.getString("id"));
-            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");
-            Timestamp lastPaymentAttemptTimeStamp = result.getTimestamp("last_payment_attempt");
-            DateTime lastPaymentAttempt = lastPaymentAttemptTimeStamp == null ? null : new DateTime(lastPaymentAttemptTimeStamp);
-            Currency currency = Currency.valueOf(result.getString("currency"));
+    void notifyFailedPayment(final String invoiceId,
+                             final String paymentId,
+                             final Date paymentAttemptDate);
 
-            return new DefaultInvoice(id, accountId, invoiceDate, targetDate, currency, lastPaymentAttempt, amountPaid, new ArrayList<InvoiceItem>());
-        }
-    }
+    void test();
 }
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
new file mode 100644
index 0000000..67e94bb
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.dao;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+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.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.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
+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 java.lang.annotation.*;
+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;
+
+@ExternalizedSqlViaStringTemplate3()
+@RegisterMapper({UuidMapper.class, InvoiceSqlDao.InvoiceMapper.class})
+public interface InvoiceSqlDao extends EntityDao<Invoice>, Transactional<InvoiceSqlDao>, Transmogrifier, CloseMe {
+    @Override
+    @SqlUpdate
+    void save(@InvoiceBinder Invoice invoice);
+
+    @SqlQuery
+    List<Invoice> getInvoicesByAccount(@Bind("accountId") final String accountId);
+
+    @SqlQuery
+    List<Invoice> getInvoicesBySubscription(@Bind("subscriptionId") final String subscriptionId);
+
+    @SqlQuery
+    List<UUID> getInvoicesForPayment(@Bind("targetDate") final Date targetDate,
+                                     @Bind("numberOfDays") final int numberOfDays);
+
+    @SqlUpdate
+    void notifySuccessfulPayment(@Bind("invoiceId") final String invoiceId,
+                                 @Bind("amount") final BigDecimal paymentAmount,
+                                 @Bind("currency") final String currency,
+                                 @Bind("paymentId") final String paymentId,
+                                 @Bind("paymentDate") final Date paymentDate);
+
+    @SqlUpdate
+    void notifyFailedPayment(@Bind("invoiceId") final String invoiceId,
+                             @Bind("paymentId") final String paymentId,
+                             @Bind("paymentAttemptDate") final Date paymentAttemptDate);
+
+    @BindingAnnotation(InvoiceBinder.InvoiceBinderFactory.class)
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target({ElementType.PARAMETER})
+    public @interface InvoiceBinder {
+        public static class InvoiceBinderFactory implements BinderFactory {
+            public Binder build(Annotation annotation) {
+                return new Binder<InvoiceBinder, Invoice>() {
+                    public void bind(SQLStatement q, InvoiceBinder bind, Invoice invoice) {
+                        q.bind("id", invoice.getId().toString());
+                        q.bind("accountId", invoice.getAccountId().toString());
+                        q.bind("invoiceDate", invoice.getInvoiceDate().toDate());
+                        q.bind("targetDate", invoice.getTargetDate().toDate());
+                        q.bind("amountPaid", invoice.getAmountPaid());
+                        q.bind("amountOutstanding", invoice.getAmountOutstanding());
+                        DateTime invoiceDate = invoice.getLastPaymentAttempt();
+                        q.bind("lastPaymentAttempt", invoiceDate == null ? null : invoiceDate.toDate());
+                        q.bind("currency", invoice.getCurrency().toString());
+                    }
+                };
+            }
+        }
+    }
+
+    public static class InvoiceMapper implements ResultSetMapper<Invoice> {
+        @Override
+        public Invoice map(int index, ResultSet result, StatementContext context) throws SQLException {
+            UUID id = UUID.fromString(result.getString("id"));
+            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");
+            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>());
+        }
+    }
+}
+
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 9b40875..54990c5 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
@@ -17,29 +17,30 @@
 package com.ning.billing.invoice.glue;
 
 import com.google.inject.AbstractModule;
+import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.invoice.api.invoice.DefaultInvoicePaymentApi;
 import com.ning.billing.invoice.api.user.DefaultInvoiceUserApi;
 import com.ning.billing.invoice.api.InvoiceUserApi;
+import com.ning.billing.invoice.dao.DefaultInvoiceDao;
 import com.ning.billing.invoice.dao.InvoiceDao;
-import com.ning.billing.invoice.dao.InvoiceDaoWrapper;
-import com.ning.billing.invoice.dao.InvoiceItemDao;
-import com.ning.billing.invoice.dao.InvoiceItemDaoWrapper;
 
 public class InvoiceModule extends AbstractModule {
     private void installInvoiceDao() {
-        bind(InvoiceDao.class).to(InvoiceDaoWrapper.class).asEagerSingleton();
-    }
-
-    private void installInvoiceItemDao() {
-        bind(InvoiceItemDao.class).to(InvoiceItemDaoWrapper.class).asEagerSingleton();
+        bind(InvoiceDao.class).to(DefaultInvoiceDao.class).asEagerSingleton();
     }
 
     protected void installInvoiceUserApi() {
         bind(InvoiceUserApi.class).to(DefaultInvoiceUserApi.class).asEagerSingleton();
     }
 
+    protected void installInvoicePaymentApi() {
+        bind(InvoicePaymentApi.class).to(DefaultInvoicePaymentApi.class).asEagerSingleton();
+    }
+
     @Override
     protected void configure() {
         installInvoiceDao();
-        installInvoiceItemDao();
+        installInvoiceUserApi();
+        installInvoicePaymentApi();
     }
 }
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
new file mode 100644
index 0000000..9414228
--- /dev/null
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg
@@ -0,0 +1,58 @@
+group InvoiceDao;
+
+getInvoicesByAccount() ::= <<
+  SELECT i.id, i.account_id, i.invoice_date, i.target_date, i.currency,
+         SUM(ip.amount) AS amount_paid, MAX(ip.payment_date) AS last_payment_attempt
+  FROM invoices i
+  LEFT JOIN invoice_payments ip ON ip.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;
+>>
+
+getInvoicesBySubscription() ::= <<
+  SELECT i.id, i.account_id, i.invoice_date, i.target_date, i.currency,
+         SUM(ip.amount) AS amount_paid, MAX(ip.payment_date) AS last_payment_attempt
+  FROM invoices i
+  INNER 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;
+>>
+
+getInvoicesForPayment() ::= <<
+  SELECT i.id
+  FROM invoices i
+  GROUP BY i.id;
+>>
+
+getById() ::= <<
+  SELECT i.id, i.account_id, i.invoice_date, i.target_date, SUM(ii.amount) AS amount, i.currency
+  FROM invoices i
+  INNER JOIN invoice_items ii ON i.id = ii.invoice_id
+  WHERE i.id = :id
+  GROUP BY i.id, i.account_id, i.invoice_date, i.target_date, i.currency;
+>>
+
+save() ::= <<
+  INSERT INTO invoices(id, account_id, invoice_date, target_date, currency)
+  VALUES (:id, :accountId, :invoiceDate, :targetDate, :currency)
+  ON DUPLICATE KEY UPDATE
+    invoice_date = :invoiceDate, target_date = :targetDate, currency = :currency;
+>>
+
+notifySuccessfulPayment() ::= <<
+  INSERT INTO invoice_payments(invoice_id, payment_id, payment_date, amount, currency)
+  VALUES(:invoiceId, :paymentId, :paymentDate, :amount, :currency);
+>>
+
+notifyFailedPayment() ::= <<
+  INSERT INTO invoice_payments(invoice_id, payment_id, payment_date)
+  VALUES(:invoiceId, :paymentId, :paymentAttemptDate);
+>>
+
+test() ::= <<
+  SELECT 1
+  FROM invoices;
+>>
+;
\ No newline at end of file
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 3c25d8e..f6f2c41 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
+++ b/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
@@ -21,8 +21,6 @@ CREATE TABLE invoices (
   invoice_date datetime NOT NULL,
   target_date datetime NOT NULL,
   currency char(3) NOT NULL,
-  amount_paid numeric(10,4) NOT NULL DEFAULT 0,
-  last_payment_attempt datetime DEFAULT NULL,
   PRIMARY KEY(id)
 ) ENGINE=innodb;
 
@@ -30,30 +28,11 @@ CREATE INDEX invoices_account_id ON invoices(account_id ASC);
 
 DROP TABLE IF EXISTS invoice_payments;
 CREATE TABLE invoice_payments (
-  id char(36) NOT NULL,
   invoice_id char(36) NOT NULL,
   payment_id char(36) NOT NULL,
   payment_date datetime NOT NULL,
-  amount numeric(10,4) NOT NULL,
-  currency char(3) NOT NULL,
-  PRIMARY KEY(id)
+  amount numeric(10,4),
+  currency char(3),
+  PRIMARY KEY(invoice_id, payment_id)
 ) ENGINE=innodb;
-CREATE UNIQUE INDEX invoice_payments_unique ON invoice_payments(invoice_id, payment_id);
-
-CREATE VIEW amount_remaining AS
-SELECT invoice_items.id,
-       SUM(invoice_items.amount) AS amount_owed
-FROM invoice_items
-GROUP BY invoice_items.id;
-
-CREATE VIEW invoices_for_payment AS
-SELECT i.id,
-       DATEDIFF(NOW(), MAX(i.last_payment_attempt)) AS days_due
-FROM invoices i
-LEFT JOIN amount_remaining ar ON i.id = ar.id
-WHERE (i.amount_paid < ar.amount_owed)
-      AND (i.last_payment_attempt IS NOT NULL)
-GROUP BY i.id;
-
-
-
+CREATE UNIQUE INDEX invoice_payments_unique ON invoice_payments(invoice_id, payment_id);
\ No newline at end of file
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 5d31a5f..268aaf5 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
@@ -30,23 +30,22 @@ import static org.testng.Assert.fail;
 
 public abstract class InvoiceDaoTestBase {
     protected InvoiceDao invoiceDao;
-    protected InvoiceItemDao invoiceItemDao;
+    protected InvoiceItemSqlDao invoiceItemDao;
 
     @BeforeClass()
     protected void setup() throws IOException {
         // Health check test to make sure MySQL is setup properly
         try {
             InvoiceModuleMock module = new InvoiceModuleMock();
-            final String ddl = IOUtils.toString(InvoiceDaoWrapper.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
+            final String ddl = IOUtils.toString(DefaultInvoiceDao.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
             module.createDb(ddl);
 
             final Injector injector = Guice.createInjector(Stage.DEVELOPMENT, module);
 
-            invoiceDao = injector.getInstance(InvoiceDaoWrapper.class);
+            invoiceDao = injector.getInstance(InvoiceDao.class);
             invoiceDao.test();
 
-            invoiceItemDao = injector.getInstance(InvoiceItemDao.class);
-            invoiceItemDao.test();
+            invoiceItemDao = module.getInvoiceItemDao();
 
             EventBusService busService = injector.getInstance(EventBusService.class);
             ((DefaultEventBusService) busService).startBus();
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 675bdeb..42c5d47 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,28 +16,22 @@
 
 package com.ning.billing.invoice.dao;
 
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.Stage;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.UUID;
+import org.joda.time.DateTime;
+import org.testng.annotations.Test;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.Invoice;
-import com.ning.billing.invoice.glue.InvoiceModuleMock;
+import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.invoice.model.DefaultInvoice;
 import com.ning.billing.invoice.model.DefaultInvoiceItem;
 import com.ning.billing.util.clock.DefaultClock;
-import com.ning.billing.util.eventbus.DefaultEventBusService;
-import com.ning.billing.util.eventbus.EventBusService;
-import org.apache.commons.io.IOUtils;
-import org.joda.time.DateTime;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
-
-import java.io.IOException;
-import java.math.BigDecimal;
-import java.util.List;
-import java.util.UUID;
 
-import static org.testng.Assert.*;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
 
 @Test(groups = {"invoicing", "invoicing-invoiceDao"})
 public class InvoiceDaoTests extends InvoiceDaoTestBase {
@@ -63,6 +57,37 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
     }
 
     @Test
+    public void testInvoicePayment() {
+        UUID accountId = UUID.randomUUID();
+        Invoice invoice = new DefaultInvoice(accountId, new DefaultClock().getUTCNow(), Currency.USD);
+        UUID invoiceId = invoice.getId();
+        UUID subscriptionId = UUID.randomUUID();
+        DateTime startDate = new DateTime(2010, 1, 1, 0, 0, 0, 0);
+        DateTime endDate = new DateTime(2010, 4, 1, 0, 0, 0, 0);
+        InvoiceItem invoiceItem = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate, endDate, "test", new BigDecimal("21.00"), new BigDecimal("7.00"), Currency.USD);
+        invoice.add(invoiceItem);
+        invoiceDao.save(invoice);
+
+        Invoice savedInvoice = invoiceDao.getById(invoiceId.toString());
+        assertNotNull(savedInvoice);
+        assertEquals(savedInvoice.getTotalAmount(), new BigDecimal("21.00"));
+        assertEquals(savedInvoice.getAmountOutstanding(), new BigDecimal("21.00"));
+        assertEquals(savedInvoice.getAmountPaid(), BigDecimal.ZERO);
+        assertEquals(savedInvoice.getItems().size(), 1);
+
+        BigDecimal paymentAmount = new BigDecimal("11.00");
+        String paymentId = UUID.randomUUID().toString();
+        invoiceDao.notifySuccessfulPayment(invoiceId.toString(), paymentAmount, Currency.USD.toString(), paymentId, new DefaultClock().getUTCNow().plusDays(12).toDate());
+
+        Invoice retrievedInvoice = invoiceDao.getById(invoiceId.toString());
+        assertNotNull(retrievedInvoice);
+        assertEquals(retrievedInvoice.getTotalAmount(), new BigDecimal("21.00"));
+        assertEquals(retrievedInvoice.getAmountOutstanding(), new BigDecimal("10.00"));
+        assertEquals(retrievedInvoice.getAmountPaid(), new BigDecimal("11.00"));
+        assertEquals(retrievedInvoice.getItems().size(), 1);
+    }
+
+    @Test
     public void testRetrievalForNonExistentInvoiceId() {
         Invoice invoice = invoiceDao.getById(UUID.randomUUID().toString());
         assertNull(invoice);
@@ -74,15 +99,16 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         DateTime targetDate = new DateTime(2011, 10, 6, 0, 0, 0, 0);
         Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD);
 
+        String paymentId = UUID.randomUUID().toString();
         DateTime paymentAttemptDate = new DateTime(2011, 6, 24, 12, 14, 36, 0);
         BigDecimal paymentAmount = new BigDecimal("14.0");
 
         invoiceDao.save(invoice);
-        invoiceDao.notifySuccessfulPayment(invoice.getId().toString(), paymentAttemptDate.toDate(), paymentAmount);
+        invoiceDao.notifySuccessfulPayment(invoice.getId().toString(), paymentAmount, Currency.USD.toString(), paymentId, paymentAttemptDate.toDate());
 
         invoice = invoiceDao.getById(invoice.getId().toString());
-        assertEquals(invoice.getAmountPaid().compareTo(paymentAmount), 0);
-        assertTrue(invoice.getLastPaymentAttempt().equals(paymentAttemptDate));
+//        assertEquals(invoice.getAmountPaid().compareTo(paymentAmount), 0);
+//        assertTrue(invoice.getLastPaymentAttempt().equals(paymentAttemptDate));
     }
 
     @Test
@@ -94,10 +120,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         DateTime paymentAttemptDate = new DateTime(2011, 6, 24, 12, 14, 36, 0);
 
         invoiceDao.save(invoice);
-        invoiceDao.notifyFailedPayment(invoice.getId().toString(), paymentAttemptDate.toDate());
+        invoiceDao.notifyFailedPayment(invoice.getId().toString(), UUID.randomUUID().toString(), paymentAttemptDate.toDate());
 
         invoice = invoiceDao.getById(invoice.getId().toString());
-        assertTrue(invoice.getLastPaymentAttempt().equals(paymentAttemptDate));
+//        assertTrue(invoice.getLastPaymentAttempt().equals(paymentAttemptDate));
     }
 
     @Test
@@ -146,7 +172,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         assertEquals(invoices.size(), existingInvoiceCount + 1);
 
         // attempt a payment; ensure that the number of invoices for payment has decreased by 1 (no retries for eight days)
-        invoiceDao.notifyFailedPayment(invoice.getId().toString(), notionalDate.toDate());
+        invoiceDao.notifyFailedPayment(invoice.getId().toString(), UUID.randomUUID().toString(), notionalDate.toDate());
         invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
         assertEquals(invoices.size(), existingInvoiceCount);
 
@@ -156,7 +182,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         assertEquals(invoices.size(), existingInvoiceCount + 1);
 
         // post successful partial payment; ensure that number of invoices for payment has decreased by 1
-        invoiceDao.notifySuccessfulPayment(invoiceId.toString(), notionalDate.toDate(), new BigDecimal("22.0000"));
+        invoiceDao.notifySuccessfulPayment(invoiceId.toString(), new BigDecimal("22.0000"), Currency.USD.toString(), UUID.randomUUID().toString(), notionalDate.toDate());
         invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
         assertEquals(invoices.size(), existingInvoiceCount);
 
@@ -170,7 +196,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         assertEquals(invoices.size(), existingInvoiceCount + 1);
 
         // post completed payment; ensure that the number of invoices for payment has decreased by 1
-        invoiceDao.notifySuccessfulPayment(invoiceId.toString(), notionalDate.toDate(), new BigDecimal("5.0000"));
+        invoiceDao.notifySuccessfulPayment(invoiceId.toString(), new BigDecimal("5.0000"), Currency.USD.toString(), UUID.randomUUID().toString(), notionalDate.toDate());
         invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
         assertEquals(invoices.size(), existingInvoiceCount);
 
diff --git a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleMock.java b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleMock.java
index ba4114b..c2f7585 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleMock.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleMock.java
@@ -17,6 +17,7 @@
 package com.ning.billing.invoice.glue;
 
 import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.invoice.dao.InvoiceItemSqlDao;
 import com.ning.billing.util.glue.EventBusModule;
 import org.skife.jdbi.v2.IDBI;
 
@@ -24,15 +25,21 @@ import java.io.IOException;
 
 public class InvoiceModuleMock extends InvoiceModule {
     private final MysqlTestingHelper helper = new MysqlTestingHelper();
+    private IDBI dbi;
 
     public void createDb(String ddl) throws IOException {
         helper.startMysql();
         helper.initDb(ddl);
     }
 
+    public InvoiceItemSqlDao getInvoiceItemDao() {
+        return dbi.onDemand(InvoiceItemSqlDao.class);
+    }
+
     @Override
     public void configure() {
-        bind(IDBI.class).toInstance(helper.getDBI());
+        dbi = helper.getDBI();
+        bind(IDBI.class).toInstance(dbi);
         super.configure();
         install(new EventBusModule());
     }
diff --git a/util/src/main/java/com/ning/billing/util/customfield/CustomizableEntityBase.java b/util/src/main/java/com/ning/billing/util/customfield/CustomizableEntityBase.java
index 00bdc04..05d0137 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/CustomizableEntityBase.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/CustomizableEntityBase.java
@@ -16,6 +16,7 @@
 
 package com.ning.billing.util.customfield;
 
+import java.util.List;
 import java.util.UUID;
 import com.ning.billing.util.entity.EntityBase;
 
@@ -38,8 +39,13 @@ public abstract class CustomizableEntityBase extends EntityBase implements Custo
     }
 
     @Override
-    public FieldStore getFields() {
-        return fields;
+    public List<CustomField> getFieldList() {
+        return fields.getEntityList();
+    }
+
+    @Override
+    public void clearFields() {
+        fields.clear();
     }
 
     public abstract String getObjectName();
diff --git a/util/src/main/java/com/ning/billing/util/entity/EntityCollectionBase.java b/util/src/main/java/com/ning/billing/util/entity/EntityCollectionBase.java
index 6f04841..369f255 100644
--- a/util/src/main/java/com/ning/billing/util/entity/EntityCollectionBase.java
+++ b/util/src/main/java/com/ning/billing/util/entity/EntityCollectionBase.java
@@ -61,21 +61,4 @@ public abstract class EntityCollectionBase<T extends Entity> implements EntityCo
     public List<T> getEntityList() {
         return new ArrayList<T>(entities.values());
     }
-//    public void save() {
-//        IEntityCollectionDao<T> dao = getCollectionDao();
-//
-//        dao.save(objectId.toString(), objectType, new ArrayList(entities.values()));
-//    }
-//
-//    public void load() {
-//        IEntityCollectionDao<T> dao = getCollectionDao();
-//
-//        List<T> entities = dao.load(objectId.toString(), objectType);
-//        this.entities.clear();
-//        if (entities != null) {
-//            for (T entity : entities) {
-//                this.entities.put(getEntityKey(entity), entity);
-//            }
-//        }
-//    }
 }
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 5a15049..8a7a8c5 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
@@ -20,6 +20,11 @@ import org.skife.jdbi.v2.sqlobject.*;
 
 import java.util.List;
 
+/**
+ * provides consistent semantics for entity collections
+ * note: this is intended to be extended by an interface which provides @ExternalizedSqlViaStringTemplate3 and mappers
+ * @param <T>
+ */
 public interface EntityCollectionDao<T extends Entity> {
     @SqlBatch
     public void save(@Bind("objectId") final String objectId,
diff --git a/util/src/main/java/com/ning/billing/util/entity/EntityDao.java b/util/src/main/java/com/ning/billing/util/entity/EntityDao.java
index 4207d5f..1ee4fc1 100644
--- a/util/src/main/java/com/ning/billing/util/entity/EntityDao.java
+++ b/util/src/main/java/com/ning/billing/util/entity/EntityDao.java
@@ -18,6 +18,7 @@ package com.ning.billing.util.entity;
 
 import org.skife.jdbi.v2.sqlobject.Bind;
 import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.skife.jdbi.v2.sqlobject.SqlBatch;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
 
diff --git a/util/src/main/java/com/ning/billing/util/glue/TagDescriptionDaoProvider.java b/util/src/main/java/com/ning/billing/util/glue/TagDescriptionDaoProvider.java
new file mode 100644
index 0000000..31fb306
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/glue/TagDescriptionDaoProvider.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.util.glue;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.ning.billing.util.tag.dao.TagDescriptionDao;
+import org.skife.jdbi.v2.IDBI;
+
+public class TagDescriptionDaoProvider implements Provider<TagDescriptionDao>
+{
+    private final IDBI dbi;
+
+    @Inject
+    public TagDescriptionDaoProvider(final IDBI dbi)
+    {
+        this.dbi = dbi;
+    }
+
+    @Override
+    public TagDescriptionDao get()
+    {
+        return dbi.onDemand(TagDescriptionDao.class);
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/glue/TagStoreDaoProvider.java b/util/src/main/java/com/ning/billing/util/glue/TagStoreDaoProvider.java
new file mode 100644
index 0000000..2c612e6
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/glue/TagStoreDaoProvider.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.util.glue;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.ning.billing.util.tag.dao.TagStoreDao;
+import org.skife.jdbi.v2.IDBI;
+
+public class TagStoreDaoProvider implements Provider<TagStoreDao>
+{
+    private final IDBI dbi;
+
+    @Inject
+    public TagStoreDaoProvider(final IDBI dbi)
+    {
+        this.dbi = dbi;
+    }
+
+    @Override
+    public TagStoreDao get()
+    {
+        return dbi.onDemand(TagStoreDao.class);
+    }
+}
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
new file mode 100644
index 0000000..ae14782
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/glue/TagStoreModule.java
@@ -0,0 +1,31 @@
+/*
+ * 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.tag.dao.TagDescriptionDao;
+import com.ning.billing.util.tag.dao.TagStoreDao;
+
+public class TagStoreModule extends AbstractModule
+{
+    @Override
+    protected void configure()
+    {
+        bind(TagDescriptionDao.class).toProvider(TagDescriptionDaoProvider.class).asEagerSingleton();
+        bind(TagStoreDao.class).toProvider(TagStoreDaoProvider.class).asEagerSingleton();
+    }
+}