killbill-uncached

analytics: handle invoice creation notifications Introduce

6/22/2012 4:22:30 PM

Details

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 458e088..2d4ab3d 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
@@ -40,14 +40,17 @@ import com.ning.billing.util.tag.api.UserTagDeletionEvent;
 public class AnalyticsListener {
     private final BusinessSubscriptionTransitionRecorder bstRecorder;
     private final BusinessAccountRecorder bacRecorder;
+    private final BusinessInvoiceRecorder invoiceRecorder;
     private final BusinessTagRecorder tagRecorder;
 
     @Inject
     public AnalyticsListener(final BusinessSubscriptionTransitionRecorder bstRecorder,
                              final BusinessAccountRecorder bacRecorder,
+                             final BusinessInvoiceRecorder invoiceRecorder,
                              final BusinessTagRecorder tagRecorder) {
         this.bstRecorder = bstRecorder;
         this.bacRecorder = bacRecorder;
+        this.invoiceRecorder = invoiceRecorder;
         this.tagRecorder = tagRecorder;
     }
 
@@ -96,11 +99,12 @@ public class AnalyticsListener {
 
     @Subscribe
     public void handleInvoiceCreation(final InvoiceCreationEvent event) {
-        bacRecorder.accountUpdated(event.getAccountId());
+        invoiceRecorder.invoiceCreated(event.getInvoiceId());
     }
 
     @Subscribe
     public void handleNullInvoice(final EmptyInvoiceEvent event) {
+        // Ignored for now
     }
 
     @Subscribe
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 7f69fef..491c06d 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessAccountRecorder.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessAccountRecorder.java
@@ -69,7 +69,7 @@ public class BusinessAccountRecorder {
         try {
             account = accountApi.getAccountByKey(data.getExternalKey());
             final Map<String, Tag> tags = tagUserApi.getTags(account.getId(), ObjectType.ACCOUNT);
-            final BusinessAccount bac = createBusinessAccountFromAccount(account, new ArrayList<Tag>(tags.values()));
+            final BusinessAccount bac = createBusinessAccountFromAccount(account);
 
             log.info("ACCOUNT CREATION " + bac);
             sqlDao.createAccount(bac);
@@ -111,16 +111,10 @@ public class BusinessAccountRecorder {
     public void accountUpdated(final UUID accountId) {
         try {
             final Account account = accountApi.getAccountById(accountId);
-            final Map<String, Tag> tags = tagUserApi.getTags(accountId, ObjectType.ACCOUNT);
-
-            if (account == null) {
-                log.warn("Couldn't find account {}", accountId);
-                return;
-            }
 
             BusinessAccount bac = sqlDao.getAccount(account.getExternalKey());
             if (bac == null) {
-                bac = createBusinessAccountFromAccount(account, new ArrayList<Tag>(tags.values()));
+                bac = createBusinessAccountFromAccount(account);
                 log.info("ACCOUNT CREATION " + bac);
                 sqlDao.createAccount(bac);
             } else {
@@ -131,10 +125,9 @@ public class BusinessAccountRecorder {
         } catch (AccountApiException e) {
             log.warn("Error encountered creating BusinessAccount", e);
         }
-
     }
 
-    private BusinessAccount createBusinessAccountFromAccount(final Account account, final List<Tag> tags) {
+    private BusinessAccount createBusinessAccountFromAccount(final Account account) {
         final BusinessAccount bac = new BusinessAccount(
                 account.getExternalKey(),
                 account.getName(),
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessInvoiceRecorder.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessInvoiceRecorder.java
new file mode 100644
index 0000000..21693f3
--- /dev/null
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessInvoiceRecorder.java
@@ -0,0 +1,138 @@
+/*
+ * 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.analytics;
+
+import javax.inject.Inject;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.analytics.dao.AnalyticsDao;
+import com.ning.billing.analytics.model.BusinessInvoice;
+import com.ning.billing.analytics.model.BusinessInvoiceItem;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+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.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+
+public class BusinessInvoiceRecorder {
+    private static final Logger log = LoggerFactory.getLogger(BusinessInvoiceRecorder.class);
+
+    private final AnalyticsDao analyticsDao;
+    private final AccountUserApi accountApi;
+    private final EntitlementUserApi entitlementApi;
+    private final InvoiceUserApi invoiceApi;
+
+    @Inject
+    public BusinessInvoiceRecorder(final AnalyticsDao analyticsDao,
+                                   final AccountUserApi accountApi,
+                                   final EntitlementUserApi entitlementApi,
+                                   final InvoiceUserApi invoiceApi) {
+        this.analyticsDao = analyticsDao;
+        this.accountApi = accountApi;
+        this.entitlementApi = entitlementApi;
+        this.invoiceApi = invoiceApi;
+    }
+
+    public void invoiceCreated(final UUID invoiceId) {
+        // Lookup the invoice object
+        final Invoice invoice = invoiceApi.getInvoice(invoiceId);
+        if (invoice == null) {
+            log.warn("Ignoring invoice creation for invoice id {} (invoice does not exist)", invoiceId.toString());
+            return;
+        }
+
+        // Lookup the associated account
+        final String accountKey;
+        try {
+            final Account account = accountApi.getAccountById(invoice.getAccountId());
+            accountKey = account.getExternalKey();
+        } catch (AccountApiException e) {
+            log.warn("Ignoring invoice creation for invoice id {} and account id {} (account does not exist)",
+                     invoice.getId().toString(),
+                     invoice.getAccountId().toString());
+            return;
+        }
+
+        // Create the invoice
+        final BusinessInvoice businessInvoice = new BusinessInvoice(accountKey, invoice);
+
+        // Create associated invoice items
+        final List<BusinessInvoiceItem> businessInvoiceItems = new ArrayList<BusinessInvoiceItem>();
+        for (final InvoiceItem invoiceItem : invoice.getInvoiceItems()) {
+            final BusinessInvoiceItem businessInvoiceItem = createBusinessInvoiceItem(invoiceItem);
+            if (businessInvoiceItem != null) {
+                businessInvoiceItems.add(businessInvoiceItem);
+            }
+        }
+
+        // Update the Analytics tables
+        analyticsDao.createInvoice(accountKey, businessInvoice, businessInvoiceItems);
+    }
+
+    private BusinessInvoiceItem createBusinessInvoiceItem(final InvoiceItem invoiceItem) {
+        final String externalKey;
+        try {
+            final SubscriptionBundle bundle = entitlementApi.getBundleFromId(invoiceItem.getBundleId());
+            externalKey = bundle.getKey();
+        } catch (EntitlementUserApiException e) {
+            log.warn("Ignoring invoice item {} for bundle {} (bundle does not exist)",
+                     invoiceItem.getId().toString(),
+                     invoiceItem.getBundleId().toString());
+            return null;
+        }
+
+        final Subscription subscription;
+        try {
+            subscription = entitlementApi.getSubscriptionFromId(invoiceItem.getSubscriptionId());
+        } catch (EntitlementUserApiException e) {
+            log.warn("Ignoring invoice item {} for subscription {} (subscription does not exist)",
+                     invoiceItem.getId().toString(),
+                     invoiceItem.getSubscriptionId().toString());
+            return null;
+        }
+
+        final Plan plan = subscription.getCurrentPlan();
+        if (plan == null) {
+            log.warn("Ignoring invoice item {} for subscription {} (null plan)",
+                     invoiceItem.getId().toString(),
+                     invoiceItem.getSubscriptionId().toString());
+            return null;
+        }
+
+        final PlanPhase planPhase = subscription.getCurrentPhase();
+        if (planPhase == null) {
+            log.warn("Ignoring invoice item {} for subscription {} (null phase)",
+                     invoiceItem.getId().toString(),
+                     invoiceItem.getSubscriptionId().toString());
+            return null;
+        }
+
+        return new BusinessInvoiceItem(externalKey, invoiceItem, plan, planPhase);
+    }
+}
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountSqlDao.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountSqlDao.java
index 09a5506..605e604 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountSqlDao.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountSqlDao.java
@@ -20,13 +20,15 @@ import org.skife.jdbi.v2.sqlobject.Bind;
 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.Transactional;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
 
 import com.ning.billing.analytics.model.BusinessAccount;
 
 @ExternalizedSqlViaStringTemplate3()
 @RegisterMapper(BusinessAccountMapper.class)
-public interface BusinessAccountSqlDao {
+public interface BusinessAccountSqlDao extends Transactional<BusinessAccountSqlDao>, Transmogrifier {
     @SqlQuery
     BusinessAccount getAccount(@Bind("account_key") final String key);
 
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessInvoiceItemSqlDao.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessInvoiceItemSqlDao.java
index 6f491d6..dd18e3a 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessInvoiceItemSqlDao.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessInvoiceItemSqlDao.java
@@ -22,13 +22,15 @@ import org.skife.jdbi.v2.sqlobject.Bind;
 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.Transactional;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
 
 import com.ning.billing.analytics.model.BusinessInvoiceItem;
 
 @ExternalizedSqlViaStringTemplate3()
 @RegisterMapper(BusinessInvoiceItemMapper.class)
-public interface BusinessInvoiceItemSqlDao {
+public interface BusinessInvoiceItemSqlDao extends Transactional<BusinessInvoiceItemSqlDao>, Transmogrifier {
     @SqlQuery
     BusinessInvoiceItem getInvoiceItem(@Bind("item_id") final String itemId);
 
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessInvoiceSqlDao.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessInvoiceSqlDao.java
index abfa302..d3c8c6e 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessInvoiceSqlDao.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessInvoiceSqlDao.java
@@ -22,13 +22,15 @@ import org.skife.jdbi.v2.sqlobject.Bind;
 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.Transactional;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
 
 import com.ning.billing.analytics.model.BusinessInvoice;
 
 @ExternalizedSqlViaStringTemplate3()
 @RegisterMapper(BusinessInvoiceMapper.class)
-public interface BusinessInvoiceSqlDao {
+public interface BusinessInvoiceSqlDao extends Transactional<BusinessInvoiceSqlDao>, Transmogrifier {
     @SqlQuery
     BusinessInvoice getInvoice(@Bind("invoice_id") final String invoiceId);
 
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/DefaultAnalyticsDao.java b/analytics/src/main/java/com/ning/billing/analytics/dao/DefaultAnalyticsDao.java
new file mode 100644
index 0000000..db18c9f
--- /dev/null
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/DefaultAnalyticsDao.java
@@ -0,0 +1,68 @@
+/*
+ * 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.analytics.dao;
+
+import javax.inject.Inject;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.skife.jdbi.v2.Transaction;
+import org.skife.jdbi.v2.TransactionStatus;
+
+import com.ning.billing.analytics.model.BusinessAccount;
+import com.ning.billing.analytics.model.BusinessInvoice;
+import com.ning.billing.analytics.model.BusinessInvoiceItem;
+
+public class DefaultAnalyticsDao implements AnalyticsDao {
+    private final BusinessInvoiceSqlDao invoiceSqlDao;
+
+    @Inject
+    public DefaultAnalyticsDao(final BusinessInvoiceSqlDao invoiceSqlDao) {
+        this.invoiceSqlDao = invoiceSqlDao;
+    }
+
+    @Override
+    public void createInvoice(final String accountKey, final BusinessInvoice invoice, final Iterable<BusinessInvoiceItem> invoiceItems) {
+        invoiceSqlDao.inTransaction(new Transaction<Void, BusinessInvoiceSqlDao>() {
+            @Override
+            public Void inTransaction(final BusinessInvoiceSqlDao transactional, final TransactionStatus status) throws Exception {
+                // Create the invoice
+                transactional.createInvoice(invoice);
+
+                // Add associated invoice items
+                final BusinessInvoiceItemSqlDao invoiceItemSqlDao = transactional.become(BusinessInvoiceItemSqlDao.class);
+                for (final BusinessInvoiceItem invoiceItem : invoiceItems) {
+                    invoiceItemSqlDao.createInvoiceItem(invoiceItem);
+                }
+
+                // Update BAC
+                final BusinessAccountSqlDao accountSqlDao = transactional.become(BusinessAccountSqlDao.class);
+                final BusinessAccount account = accountSqlDao.getAccount(accountKey);
+                if (account == null) {
+                    throw new IllegalStateException("Account does not exist for key " + accountKey);
+                }
+                account.setBalance(account.getBalance().add(invoice.getBalance()));
+                account.setLastInvoiceDate(invoice.getInvoiceDate());
+                account.setTotalInvoiceBalance(account.getTotalInvoiceBalance().add(invoice.getBalance()));
+                account.setUpdatedDt(new DateTime(DateTimeZone.UTC));
+                accountSqlDao.saveAccount(account);
+
+                return null;
+            }
+        });
+    }
+}
diff --git a/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java b/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java
index c59edfe..3d3c2e4 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java
@@ -24,6 +24,7 @@ import com.ning.billing.analytics.BusinessSubscriptionTransitionRecorder;
 import com.ning.billing.analytics.BusinessTagRecorder;
 import com.ning.billing.analytics.api.AnalyticsService;
 import com.ning.billing.analytics.api.DefaultAnalyticsService;
+import com.ning.billing.analytics.dao.AnalyticsDao;
 import com.ning.billing.analytics.dao.BusinessAccountSqlDao;
 import com.ning.billing.analytics.dao.BusinessAccountTagSqlDao;
 import com.ning.billing.analytics.dao.BusinessInvoiceFieldSqlDao;
@@ -38,6 +39,7 @@ import com.ning.billing.analytics.dao.BusinessSqlProvider;
 import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionFieldSqlDao;
 import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionSqlDao;
 import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionTagSqlDao;
+import com.ning.billing.analytics.dao.DefaultAnalyticsDao;
 
 public class AnalyticsModule extends AbstractModule {
     @Override
@@ -61,6 +63,7 @@ public class AnalyticsModule extends AbstractModule {
         bind(BusinessTagRecorder.class).asEagerSingleton();
         bind(AnalyticsListener.class).asEagerSingleton();
 
+        bind(AnalyticsDao.class).to(DefaultAnalyticsDao.class).asEagerSingleton();
         bind(AnalyticsService.class).to(DefaultAnalyticsService.class).asEagerSingleton();
     }
 }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/dao/TestDefaultAnalyticsDao.java b/analytics/src/test/java/com/ning/billing/analytics/dao/TestDefaultAnalyticsDao.java
new file mode 100644
index 0000000..faa022c
--- /dev/null
+++ b/analytics/src/test/java/com/ning/billing/analytics/dao/TestDefaultAnalyticsDao.java
@@ -0,0 +1,123 @@
+/*
+ * 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.analytics.dao;
+
+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.skife.jdbi.v2.IDBI;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.ning.billing.analytics.TestWithEmbeddedDB;
+import com.ning.billing.analytics.model.BusinessAccount;
+import com.ning.billing.analytics.model.BusinessInvoice;
+import com.ning.billing.analytics.model.BusinessInvoiceItem;
+import com.ning.billing.catalog.api.Currency;
+
+public class TestDefaultAnalyticsDao extends TestWithEmbeddedDB {
+    private BusinessAccountSqlDao accountSqlDao;
+    private BusinessInvoiceSqlDao invoiceSqlDao;
+    private BusinessInvoiceItemSqlDao invoiceItemSqlDao;
+    private AnalyticsDao analyticsDao;
+
+    @BeforeMethod(groups = "slow")
+    public void setUp() throws Exception {
+        final IDBI dbi = helper.getDBI();
+        accountSqlDao = dbi.onDemand(BusinessAccountSqlDao.class);
+        invoiceSqlDao = dbi.onDemand(BusinessInvoiceSqlDao.class);
+        invoiceItemSqlDao = dbi.onDemand(BusinessInvoiceItemSqlDao.class);
+        analyticsDao = new DefaultAnalyticsDao(invoiceSqlDao);
+    }
+
+    @Test(groups = "slow")
+    public void testCreateInvoice() throws Exception {
+        // Create and verify the initial state
+        BusinessAccount account = new BusinessAccount(UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                      BigDecimal.ONE, new DateTime(DateTimeZone.UTC), BigDecimal.TEN,
+                                                      "ERROR_NOT_ENOUGH_FUNDS", "CreditCard", "Visa", "FRANCE");
+        Assert.assertEquals(accountSqlDao.createAccount(account), 1);
+        Assert.assertEquals(invoiceSqlDao.getInvoicesForAccount(account.getKey()).size(), 0);
+        account = accountSqlDao.getAccount(account.getKey());
+
+        // Generate the invoices
+        final BusinessInvoice invoice = createInvoice(account.getKey());
+        final List<BusinessInvoiceItem> invoiceItems = new ArrayList<BusinessInvoiceItem>();
+        for (int i = 0; i < 10; i++) {
+            invoiceItems.add(createInvoiceItem(invoice.getInvoiceId(), BigDecimal.valueOf(1242 + i)));
+        }
+        analyticsDao.createInvoice(account.getKey(), invoice, invoiceItems);
+
+        // Verify the final state
+        final List<BusinessInvoice> invoicesForAccount = invoiceSqlDao.getInvoicesForAccount(account.getKey());
+        Assert.assertEquals(invoicesForAccount.size(), 1);
+        Assert.assertEquals(invoicesForAccount.get(0).getInvoiceId(), invoice.getInvoiceId());
+
+        Assert.assertEquals(invoiceItemSqlDao.getInvoiceItemsForInvoice(invoice.getInvoiceId().toString()).size(), 10);
+
+        final BusinessAccount finalAccount = accountSqlDao.getAccount(account.getKey());
+        Assert.assertEquals(finalAccount.getCreatedDt(), account.getCreatedDt());
+        Assert.assertTrue(finalAccount.getUpdatedDt().isAfter(account.getCreatedDt()));
+        Assert.assertTrue(finalAccount.getUpdatedDt().isAfter(account.getUpdatedDt()));
+        Assert.assertTrue(finalAccount.getLastInvoiceDate().equals(invoice.getInvoiceDate()));
+        // invoice.getBalance() is not the sum of all the items here - but in practice it will be
+        Assert.assertEquals(finalAccount.getTotalInvoiceBalance(), account.getTotalInvoiceBalance().add(invoice.getBalance()));
+    }
+
+    private BusinessInvoice createInvoice(final String accountKey) {
+        final BigDecimal amountCharged = BigDecimal.ZERO;
+        final BigDecimal amountCredited = BigDecimal.ONE;
+        final BigDecimal amountPaid = BigDecimal.TEN;
+        final BigDecimal balance = BigDecimal.valueOf(123L);
+        final DateTime createdDate = new DateTime(DateTimeZone.UTC);
+        final Currency currency = Currency.MXN;
+        final DateTime invoiceDate = new DateTime(DateTimeZone.UTC);
+        final UUID invoiceId = UUID.randomUUID();
+        final DateTime targetDate = new DateTime(DateTimeZone.UTC);
+        final DateTime updatedDate = new DateTime(DateTimeZone.UTC);
+
+        return new BusinessInvoice(accountKey, amountCharged, amountCredited, amountPaid, balance,
+                                   createdDate, currency, invoiceDate, invoiceId, targetDate, updatedDate);
+    }
+
+    private BusinessInvoiceItem createInvoiceItem(final UUID invoiceId, final BigDecimal amount) {
+        final String billingPeriod = UUID.randomUUID().toString().substring(0, 20);
+        final DateTime createdDate = new DateTime(DateTimeZone.UTC);
+        final Currency currency = Currency.AUD;
+        final DateTime endDate = new DateTime(DateTimeZone.UTC);
+        final String externalKey = UUID.randomUUID().toString();
+        final UUID itemId = UUID.randomUUID();
+        final String itemType = UUID.randomUUID().toString().substring(0, 20);
+        final String phase = UUID.randomUUID().toString().substring(0, 20);
+        final String productCategory = UUID.randomUUID().toString().substring(0, 20);
+        final String productName = UUID.randomUUID().toString().substring(0, 20);
+        final String productType = UUID.randomUUID().toString().substring(0, 20);
+        final String slug = UUID.randomUUID().toString().substring(0, 20);
+        final DateTime startDate = new DateTime(DateTimeZone.UTC);
+        final DateTime updatedDate = new DateTime(DateTimeZone.UTC);
+
+        return new BusinessInvoiceItem(amount, billingPeriod, createdDate, currency,
+                                       endDate, externalKey, invoiceId, itemId, itemType,
+                                       phase, productCategory, productName, productType,
+                                       slug, startDate, updatedDate);
+    }
+}
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 58fbc6d..a5cfc42 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
@@ -74,7 +74,7 @@ public class TestAnalyticsListener extends AnalyticsTestSuite {
     @BeforeMethod(groups = "fast")
     public void setUp() throws Exception {
         final BusinessSubscriptionTransitionRecorder recorder = new BusinessSubscriptionTransitionRecorder(dao, catalogService, new MockEntitlementUserApi(bundleUUID, EXTERNAL_KEY), new MockAccountUserApi(ACCOUNT_KEY, CURRENCY));
-        listener = new AnalyticsListener(recorder, null, null);
+        listener = new AnalyticsListener(recorder, null, null, null);
     }
 
     @Test(groups = "fast")