killbill-aplcache

analytics: integrate invoice and payment with BusinessAccountRecorder BAC

2/28/2012 9:34:19 PM

Details

analytics/pom.xml 10(+10 -0)

diff --git a/analytics/pom.xml b/analytics/pom.xml
index 10f94d4..ad46c31 100644
--- a/analytics/pom.xml
+++ b/analytics/pom.xml
@@ -89,6 +89,16 @@
         </dependency>
         <dependency>
             <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-invoice</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-payment</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
             <artifactId>killbill-util</artifactId>
             <scope>test</scope>
         </dependency>
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 1735062..ba9dc2a 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
@@ -22,22 +22,22 @@ import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.account.api.AccountChangeNotification;
 import com.ning.billing.account.api.AccountCreationNotification;
 import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+import com.ning.billing.invoice.api.InvoiceCreationNotification;
+import com.ning.billing.payment.api.PaymentError;
+import com.ning.billing.payment.api.PaymentInfo;
 
-public class AnalyticsListener
-{
+public class AnalyticsListener {
     private final BusinessSubscriptionTransitionRecorder bstRecorder;
     private final BusinessAccountRecorder bacRecorder;
 
     @Inject
-    public AnalyticsListener(final BusinessSubscriptionTransitionRecorder bstRecorder, final BusinessAccountRecorder bacRecorder)
-    {
+    public AnalyticsListener(final BusinessSubscriptionTransitionRecorder bstRecorder, final BusinessAccountRecorder bacRecorder) {
         this.bstRecorder = bstRecorder;
         this.bacRecorder = bacRecorder;
     }
 
     @Subscribe
-    public void handleSubscriptionTransitionChange(final SubscriptionTransition event) throws AccountApiException
-    {
+    public void handleSubscriptionTransitionChange(final SubscriptionTransition event) throws AccountApiException {
         switch (event.getTransitionType()) {
             case MIGRATE_ENTITLEMENT:
                 // TODO do nothing for now
@@ -68,18 +68,31 @@ public class AnalyticsListener
     }
 
     @Subscribe
-    public void handleAccountCreation(final AccountCreationNotification event)
-    {
+    public void handleAccountCreation(final AccountCreationNotification event) {
         bacRecorder.accountCreated(event.getData());
     }
 
     @Subscribe
-    public void handleAccountChange(final AccountChangeNotification event)
-    {
+    public void handleAccountChange(final AccountChangeNotification event) {
         if (!event.hasChanges()) {
             return;
         }
 
         bacRecorder.accountUpdated(event.getAccountId(), event.getChangedFields());
     }
+
+    @Subscribe
+    public void handleInvoice(final InvoiceCreationNotification event) {
+        bacRecorder.accountUpdated(event.getAccountId());
+    }
+
+    @Subscribe
+    public void handlePaymentInfo(final PaymentInfo paymentInfo) {
+        bacRecorder.accountUpdated(paymentInfo);
+    }
+
+    @Subscribe
+    public void handlePaymentError(final PaymentError paymentError) {
+        // TODO - we can't tie the error back to an account yet
+    }
 }
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 f7081c7..31d1e6c 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessAccountRecorder.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessAccountRecorder.java
@@ -22,57 +22,164 @@ 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.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+import com.ning.billing.payment.api.PaymentApi;
+import com.ning.billing.payment.api.PaymentAttempt;
+import com.ning.billing.payment.api.PaymentInfo;
 import com.ning.billing.util.tag.Tag;
+import org.joda.time.DateTime;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
 
-public class BusinessAccountRecorder
-{
+public class BusinessAccountRecorder {
     private static final Logger log = LoggerFactory.getLogger(BusinessAccountRecorder.class);
 
     private final BusinessAccountDao dao;
     private final AccountUserApi accountApi;
+    private final InvoiceUserApi invoiceUserApi;
+    private final PaymentApi paymentApi;
 
     @Inject
-    public BusinessAccountRecorder(final BusinessAccountDao dao, final AccountUserApi accountApi)
-    {
+    public BusinessAccountRecorder(final BusinessAccountDao dao, final AccountUserApi accountApi, final InvoiceUserApi invoiceUserApi, final PaymentApi paymentApi) {
         this.dao = dao;
         this.accountApi = accountApi;
+        this.invoiceUserApi = invoiceUserApi;
+        this.paymentApi = paymentApi;
     }
 
-    public void accountCreated(final AccountData data)
-    {
+    public void accountCreated(final AccountData data) {
         final Account account = accountApi.getAccountByKey(data.getExternalKey());
+        final BusinessAccount bac = createBusinessAccountFromAccount(account);
 
+        log.info("ACCOUNT CREATION " + bac);
+        dao.createAccount(bac);
+    }
+
+    /**
+     * Notification handler for Account changes
+     *
+     * @param accountId     account id changed
+     * @param changedFields list of changed fields
+     */
+    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
+    }
+
+    /**
+     * Notification handler for Payment creations
+     *
+     * @param paymentInfo payment object (from the payment plugin)
+     */
+    public void accountUpdated(final PaymentInfo paymentInfo) {
+        final PaymentAttempt paymentAttempt = paymentApi.getPaymentAttemptForPaymentId(paymentInfo.getPaymentId());
+        if (paymentAttempt == null) {
+            return;
+        }
+
+        final Account account = accountApi.getAccountById(paymentAttempt.getAccountId());
+        if (account == null) {
+            return;
+        }
+
+        accountUpdated(account.getId());
+    }
+
+    /**
+     * Notification handler for Invoice creations
+     *
+     * @param accountId account id associated with the created invoice
+     */
+    public void accountUpdated(final UUID accountId) {
+        final Account account = accountApi.getAccountById(accountId);
+        BusinessAccount bac = dao.getAccount(accountId.toString());
+
+        if (account == null) {
+            log.warn("Couldn't find account {}", accountId);
+        } else if (bac == null) {
+            bac = createBusinessAccountFromAccount(account);
+            log.info("ACCOUNT CREATION " + bac);
+            dao.createAccount(bac);
+        } else {
+            updateBusinessAccountFromAccount(account, bac);
+            log.info("ACCOUNT UPDATE " + bac);
+            dao.saveAccount(bac);
+        }
+    }
+
+    private BusinessAccount createBusinessAccountFromAccount(final Account account) {
         final List<String> tags = new ArrayList<String>();
         for (final Tag tag : account.getTagList()) {
             tags.add(tag.getTagDefinitionName());
         }
 
-        // 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
+                account.getExternalKey(),
+                null, // TODO We need an API for the account balance
+                tags,
+                // These fields will be updated below
+                null,
+                null,
+                null,
+                null,
+                null,
+                null
         );
+        updateBusinessAccountFromAccount(account, bac);
 
-        log.info("ACCOUNT CREATION " + bac);
-        dao.createAccount(bac);
+        return bac;
     }
 
-    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
+    private void updateBusinessAccountFromAccount(final Account account, final BusinessAccount bac) {
+        DateTime lastInvoiceDate = null;
+        BigDecimal totalInvoiceBalance = BigDecimal.ZERO;
+        String lastPaymentStatus = null;
+        String paymentMethod = null;
+        String creditCardType = null;
+        String billingAddressCountry = null;
+
+        // Retrieve invoices information
+        final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId());
+        if (invoices != null && invoices.size() > 0) {
+            final List<String> invoiceIds = new ArrayList<String>();
+            for (final Invoice invoice : invoices) {
+                invoiceIds.add(invoice.getId().toString());
+                totalInvoiceBalance = totalInvoiceBalance.add(invoice.getBalance());
+
+                if (lastInvoiceDate == null || invoice.getInvoiceDate().isAfter(lastInvoiceDate)) {
+                    lastInvoiceDate = invoice.getInvoiceDate();
+                }
+            }
+
+            // Retrieve payments information for these invoices
+            DateTime lastPaymentDate = null;
+            final List<PaymentInfo> payments = paymentApi.getPaymentInfo(invoiceIds);
+            if (payments != null) {
+                for (final PaymentInfo payment : payments) {
+                    // Use the last payment method/type/country as the default one for the account
+                    if (lastPaymentDate == null || payment.getCreatedDate().isAfter(lastPaymentDate)) {
+                        lastPaymentDate = payment.getCreatedDate();
+
+                        lastPaymentStatus = payment.getStatus();
+                        paymentMethod = payment.getPaymentMethod();
+                        creditCardType = payment.getCardType();
+                        billingAddressCountry = payment.getCardCountry();
+                    }
+                }
+            }
+        }
+
+        bac.setLastPaymentStatus(lastPaymentStatus);
+        bac.setPaymentMethod(paymentMethod);
+        bac.setCreditCardType(creditCardType);
+        bac.setBillingAddressCountry(billingAddressCountry);
+        bac.setLastInvoiceDate(lastInvoiceDate);
+        bac.setTotalInvoiceBalance(totalInvoiceBalance);
     }
 }
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 25496b5..5c7ffae 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
@@ -16,6 +16,8 @@
 
 package com.ning.billing.analytics;
 
+import com.ning.billing.invoice.glue.InvoiceModule;
+import com.ning.billing.payment.setup.PaymentModule;
 import org.skife.jdbi.v2.IDBI;
 import com.ning.billing.account.glue.AccountModule;
 import com.ning.billing.analytics.setup.AnalyticsModule;
@@ -41,6 +43,8 @@ public class AnalyticsTestModule extends AnalyticsModule
         install(new CatalogModule());
         install(new BusModule());
         install(new EntitlementModule());
+        install(new InvoiceModule());
+        install(new PaymentModule());
         install(new ClockModule());
         install(new TagStoreModule());
         install(new NotificationQueueModule());
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 c413f0d..83419a2 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
@@ -139,12 +139,16 @@ public class TestAnalyticsService
         final String analyticsDdl = IOUtils.toString(BusinessSubscriptionTransitionDao.class.getResourceAsStream("/com/ning/billing/analytics/ddl.sql"));
         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"));
+        final String invoiceDdl = IOUtils.toString(BusinessSubscriptionTransitionDao.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
+        final String paymentDdl = IOUtils.toString(BusinessSubscriptionTransitionDao.class.getResourceAsStream("/com/ning/billing/payment/ddl.sql"));
         final String utilDdl = IOUtils.toString(BusinessSubscriptionTransitionDao.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
 
         helper.startMysql();
         helper.initDb(analyticsDdl);
         helper.initDb(accountDdl);
         helper.initDb(entitlementDdl);
+        helper.initDb(invoiceDdl);
+        helper.initDb(paymentDdl);
         helper.initDb(utilDdl);
     }
 
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.java b/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.java
index b505fa0..8c1611d 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.java
@@ -20,6 +20,7 @@ import java.util.Date;
 import java.util.List;
 import java.util.UUID;
 
+import com.google.common.collect.ImmutableList;
 import com.ning.billing.util.clock.Clock;
 import org.joda.time.DateTime;
 import org.skife.jdbi.v2.IDBI;
@@ -29,6 +30,8 @@ import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.payment.api.PaymentAttempt;
 import com.ning.billing.payment.api.PaymentInfo;
 
+import javax.annotation.concurrent.Immutable;
+
 public class DefaultPaymentDao implements PaymentDao {
     private final PaymentSqlDao sqlDao;
     private final Clock clock;
@@ -80,12 +83,20 @@ public class DefaultPaymentDao implements PaymentDao {
 
     @Override
     public List<PaymentInfo> getPaymentInfo(List<String> invoiceIds) {
-        return sqlDao.getPaymentInfos(invoiceIds);
+        if (invoiceIds == null || invoiceIds.size() == 0) {
+            return ImmutableList.<PaymentInfo>of();
+        } else {
+            return sqlDao.getPaymentInfos(invoiceIds);
+        }
     }
 
     @Override
     public List<PaymentAttempt> getPaymentAttemptsForInvoiceIds(List<String> invoiceIds) {
-        return sqlDao.getPaymentAttemptsForInvoiceIds(invoiceIds);
+        if (invoiceIds == null || invoiceIds.size() == 0) {
+            return ImmutableList.<PaymentAttempt>of();
+        } else {
+            return sqlDao.getPaymentAttemptsForInvoiceIds(invoiceIds);
+        }
     }
 
     @Override