killbill-memoizeit

Changes

analytics/pom.xml 12(+9 -3)

Details

analytics/pom.xml 12(+9 -3)

diff --git a/analytics/pom.xml b/analytics/pom.xml
index 265bcf5..4c477eb 100644
--- a/analytics/pom.xml
+++ b/analytics/pom.xml
@@ -8,7 +8,8 @@
     OR CONDITIONS OF ANY KIND, either express or implied. See the ~ License for 
     the specific language governing permissions and limitations ~ under the License. -->
 
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <groupId>com.ning.billing</groupId>
@@ -21,8 +22,9 @@
     <packaging>jar</packaging>
     <dependencies>
         <dependency>
-            <groupId>org.jdbi</groupId>
-            <artifactId>jdbi</artifactId>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>com.google.inject</groupId>
@@ -63,6 +65,10 @@
             <scope>runtime</scope>
         </dependency>
         <dependency>
+            <groupId>org.jdbi</groupId>
+            <artifactId>jdbi</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.testng</groupId>
             <artifactId>testng</artifactId>
             <scope>test</scope>
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 653a285..63c7a06 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
@@ -19,7 +19,7 @@ package com.ning.billing.analytics;
 import com.google.inject.Inject;
 import com.ning.billing.account.api.IAccount;
 import com.ning.billing.account.api.IAccountUserApi;
-import com.ning.billing.analytics.dao.EventDao;
+import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionDao;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.entitlement.api.user.IApiListener;
 import com.ning.billing.entitlement.api.user.IEntitlementUserApi;
@@ -34,11 +34,13 @@ import java.util.List;
 public class AnalyticsListener implements IApiListener
 {
     private static final Logger log = LoggerFactory.getLogger(AnalyticsListener.class);
-    private final EventDao dao;
+
+    private final BusinessSubscriptionTransitionDao dao;
     private final IEntitlementUserApi entitlementApi;
     private final IAccountUserApi accountApi;
 
-    public AnalyticsListener(final EventDao dao, final IEntitlementUserApi entitlementApi, final IAccountUserApi accountApi)
+    @Inject
+    public AnalyticsListener(final BusinessSubscriptionTransitionDao dao, final IEntitlementUserApi entitlementApi, final IAccountUserApi accountApi)
     {
         this.dao = dao;
         this.entitlementApi = entitlementApi;
@@ -46,42 +48,42 @@ public class AnalyticsListener implements IApiListener
     }
 
     @Override
-    public void subscriptionCreated(ISubscriptionTransition created)
+    public void subscriptionCreated(final ISubscriptionTransition created)
     {
         final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCreated(created.getNextPlan());
         recordTransition(event, created);
     }
 
     @Override
-    public void subscriptionCancelled(ISubscriptionTransition cancelled)
+    public void subscriptionCancelled(final ISubscriptionTransition cancelled)
     {
         final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCancelled(cancelled.getNextPlan());
         recordTransition(event, cancelled);
     }
 
     @Override
-    public void subscriptionChanged(ISubscriptionTransition changed)
+    public void subscriptionChanged(final ISubscriptionTransition changed)
     {
         final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionChanged(changed.getNextPlan());
         recordTransition(event, changed);
     }
 
     @Override
-    public void subscriptionPaused(ISubscriptionTransition paused)
+    public void subscriptionPaused(final ISubscriptionTransition paused)
     {
         final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionPaused(paused.getNextPlan());
         recordTransition(event, paused);
     }
 
     @Override
-    public void subscriptionResumed(ISubscriptionTransition resumed)
+    public void subscriptionResumed(final ISubscriptionTransition resumed)
     {
         final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionResumed(resumed.getNextPlan());
         recordTransition(event, resumed);
     }
 
     @Override
-    public void subscriptionPhaseChanged(ISubscriptionTransition phaseChanged)
+    public void subscriptionPhaseChanged(final ISubscriptionTransition phaseChanged)
     {
         final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionPhaseChanged(phaseChanged.getNextPlan(), phaseChanged.getNextState());
         recordTransition(event, phaseChanged);
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessAccount.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessAccount.java
new file mode 100644
index 0000000..aaf75a1
--- /dev/null
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessAccount.java
@@ -0,0 +1,254 @@
+/*
+ * 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.analytics;
+
+import com.ning.billing.analytics.utils.Rounder;
+import org.joda.time.DateTime;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+public class BusinessAccount
+{
+    // Populated by the database
+    private DateTime createdDt = null;
+    private DateTime updatedDt = null;
+
+    private final String key;
+    private BigDecimal balance;
+    private List<String> tags;
+    private DateTime lastInvoiceDate;
+    private BigDecimal totalInvoiceBalance;
+    private String lastPaymentStatus;
+    private String paymentMethod;
+    private String creditCardType;
+    private String billingAddressCountry;
+
+    public BusinessAccount(final String key, final BigDecimal balance, final List<String> tags, final DateTime lastInvoiceDate, final BigDecimal totalInvoiceBalance, final String lastPaymentStatus, final String paymentMethod, final String creditCardType, final String billingAddressCountry)
+    {
+        this.key = key;
+        this.balance = balance;
+        this.billingAddressCountry = billingAddressCountry;
+        this.creditCardType = creditCardType;
+        this.lastInvoiceDate = lastInvoiceDate;
+        this.lastPaymentStatus = lastPaymentStatus;
+        this.paymentMethod = paymentMethod;
+        this.tags = tags;
+        this.totalInvoiceBalance = totalInvoiceBalance;
+    }
+
+    public String getKey()
+    {
+        return key;
+    }
+
+    public BigDecimal getBalance()
+    {
+        return balance;
+    }
+
+    public Double getRoundedBalance()
+    {
+        return Rounder.round(balance);
+    }
+
+    public void setBalance(final BigDecimal balance)
+    {
+        this.balance = balance;
+    }
+
+    public String getBillingAddressCountry()
+    {
+        return billingAddressCountry;
+    }
+
+    public void setBillingAddressCountry(final String billingAddressCountry)
+    {
+        this.billingAddressCountry = billingAddressCountry;
+    }
+
+    public DateTime getCreatedDt()
+    {
+        return createdDt;
+    }
+
+    public void setCreatedDt(final DateTime createdDt)
+    {
+        this.createdDt = createdDt;
+    }
+
+    public String getCreditCardType()
+    {
+        return creditCardType;
+    }
+
+    public void setCreditCardType(final String creditCardType)
+    {
+        this.creditCardType = creditCardType;
+    }
+
+    public DateTime getLastInvoiceDate()
+    {
+        return lastInvoiceDate;
+    }
+
+    public void setLastInvoiceDate(final DateTime lastInvoiceDate)
+    {
+        this.lastInvoiceDate = lastInvoiceDate;
+    }
+
+    public String getLastPaymentStatus()
+    {
+        return lastPaymentStatus;
+    }
+
+    public void setLastPaymentStatus(final String lastPaymentStatus)
+    {
+        this.lastPaymentStatus = lastPaymentStatus;
+    }
+
+    public String getPaymentMethod()
+    {
+        return paymentMethod;
+    }
+
+    public void setPaymentMethod(final String paymentMethod)
+    {
+        this.paymentMethod = paymentMethod;
+    }
+
+    public List<String> getTags()
+    {
+        return tags;
+    }
+
+    public void setTags(final List<String> tags)
+    {
+        this.tags = tags;
+    }
+
+    public BigDecimal getTotalInvoiceBalance()
+    {
+        return totalInvoiceBalance;
+    }
+
+    public Double getRoundedTotalInvoiceBalance()
+    {
+        return Rounder.round(totalInvoiceBalance);
+    }
+
+    public void setTotalInvoiceBalance(final BigDecimal totalInvoiceBalance)
+    {
+        this.totalInvoiceBalance = totalInvoiceBalance;
+    }
+
+    public DateTime getUpdatedDt()
+    {
+        return updatedDt;
+    }
+
+    public void setUpdatedDt(final DateTime updatedDt)
+    {
+        this.updatedDt = updatedDt;
+    }
+
+    @Override
+    public String toString()
+    {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("BusinessAccount");
+        sb.append("{balance=").append(balance);
+        sb.append(", createdDt=").append(createdDt);
+        sb.append(", updatedDt=").append(updatedDt);
+        sb.append(", key='").append(key).append('\'');
+        sb.append(", tags=").append(tags);
+        sb.append(", lastInvoiceDate=").append(lastInvoiceDate);
+        sb.append(", totalInvoiceBalance=").append(totalInvoiceBalance);
+        sb.append(", lastPaymentStatus='").append(lastPaymentStatus).append('\'');
+        sb.append(", paymentMethod='").append(paymentMethod).append('\'');
+        sb.append(", creditCardType='").append(creditCardType).append('\'');
+        sb.append(", billingAddressCountry='").append(billingAddressCountry).append('\'');
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o)
+    {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final BusinessAccount that = (BusinessAccount) o;
+
+        if (balance != null ? !(Rounder.round(balance) == Rounder.round(that.balance)) : that.balance != null) {
+            return false;
+        }
+        if (billingAddressCountry != null ? !billingAddressCountry.equals(that.billingAddressCountry) : that.billingAddressCountry != null) {
+            return false;
+        }
+        if (createdDt != null ? !createdDt.equals(that.createdDt) : that.createdDt != null) {
+            return false;
+        }
+        if (creditCardType != null ? !creditCardType.equals(that.creditCardType) : that.creditCardType != null) {
+            return false;
+        }
+        if (key != null ? !key.equals(that.key) : that.key != null) {
+            return false;
+        }
+        if (lastInvoiceDate != null ? !lastInvoiceDate.equals(that.lastInvoiceDate) : that.lastInvoiceDate != null) {
+            return false;
+        }
+        if (lastPaymentStatus != null ? !lastPaymentStatus.equals(that.lastPaymentStatus) : that.lastPaymentStatus != null) {
+            return false;
+        }
+        if (paymentMethod != null ? !paymentMethod.equals(that.paymentMethod) : that.paymentMethod != null) {
+            return false;
+        }
+        if (tags != null ? !tags.equals(that.tags) : that.tags != null) {
+            return false;
+        }
+        if (totalInvoiceBalance != null ? !(Rounder.round(totalInvoiceBalance) == Rounder.round(that.totalInvoiceBalance)) : that.totalInvoiceBalance != null) {
+            return false;
+        }
+        if (updatedDt != null ? !updatedDt.equals(that.updatedDt) : that.updatedDt != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode()
+    {
+        int result = createdDt != null ? createdDt.hashCode() : 0;
+        result = 31 * result + (updatedDt != null ? updatedDt.hashCode() : 0);
+        result = 31 * result + (key != null ? key.hashCode() : 0);
+        result = 31 * result + (balance != null ? balance.hashCode() : 0);
+        result = 31 * result + (tags != null ? tags.hashCode() : 0);
+        result = 31 * result + (lastInvoiceDate != null ? lastInvoiceDate.hashCode() : 0);
+        result = 31 * result + (totalInvoiceBalance != null ? totalInvoiceBalance.hashCode() : 0);
+        result = 31 * result + (lastPaymentStatus != null ? lastPaymentStatus.hashCode() : 0);
+        result = 31 * result + (paymentMethod != null ? paymentMethod.hashCode() : 0);
+        result = 31 * result + (creditCardType != null ? creditCardType.hashCode() : 0);
+        result = 31 * result + (billingAddressCountry != null ? billingAddressCountry.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java
index 474c854..b85f6f3 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java
@@ -16,6 +16,7 @@
 
 package com.ning.billing.analytics;
 
+import com.ning.billing.analytics.utils.Rounder;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.IDuration;
 import com.ning.billing.catalog.api.IPlan;
@@ -44,13 +45,13 @@ public class BusinessSubscription
     private static final BigDecimal DAYS_IN_MONTH = BigDecimal.valueOf(30);
     private static final BigDecimal MONTHS_IN_YEAR = BigDecimal.valueOf(12);
     private static final Currency USD = Currency.valueOf("USD");
-    private static final int SCALE = 4;
 
     private final String productName;
     private final String productType;
     private final ProductCategory productCategory;
     private final String slug;
     private final String phase;
+    private final String billingPeriod;
     private final BigDecimal price;
     private final BigDecimal mrr;
     private final String currency;
@@ -59,13 +60,14 @@ public class BusinessSubscription
     private final UUID subscriptionId;
     private final UUID bundleId;
 
-    public BusinessSubscription(final String productName, final String productType, final ProductCategory productCategory, final String slug, final String phase, final BigDecimal price, final BigDecimal mrr, final String currency, final DateTime startDate, final SubscriptionState state, final UUID subscriptionId, final UUID bundleId)
+    public BusinessSubscription(final String productName, final String productType, final ProductCategory productCategory, final String slug, final String phase, final String billingPeriod, final BigDecimal price, final BigDecimal mrr, final String currency, final DateTime startDate, final SubscriptionState state, final UUID subscriptionId, final UUID bundleId)
     {
         this.productName = productName;
         this.productType = productType;
         this.productCategory = productCategory;
         this.slug = slug;
         this.phase = phase;
+        this.billingPeriod = billingPeriod;
         this.price = price;
         this.mrr = mrr;
         this.currency = currency;
@@ -111,9 +113,11 @@ public class BusinessSubscription
 
             if (currentPhase.getPhaseType() != null) {
                 phase = currentPhase.getPhaseType().toString();
+                billingPeriod = currentPhase.getBillingPeriod().toString();
             }
             else {
                 phase = null;
+                billingPeriod = null;
             }
 
             if (currentPhase.getRecurringPrice() != null) {
@@ -128,6 +132,7 @@ public class BusinessSubscription
         else {
             slug = null;
             phase = null;
+            billingPeriod = null;
             price = null;
             mrr = null;
         }
@@ -145,6 +150,11 @@ public class BusinessSubscription
         this.bundleId = bundleId;
     }
 
+    public String getBillingPeriod()
+    {
+        return billingPeriod;
+    }
+
     public UUID getBundleId()
     {
         return bundleId;
@@ -162,7 +172,7 @@ public class BusinessSubscription
 
     public double getRoundedMrr()
     {
-        return round(mrr);
+        return Rounder.round(mrr);
     }
 
     public String getPhase()
@@ -177,7 +187,7 @@ public class BusinessSubscription
 
     public double getRoundedPrice()
     {
-        return round(price);
+        return Rounder.round(price);
     }
 
     public ProductCategory getProductCategory()
@@ -228,10 +238,10 @@ public class BusinessSubscription
             return price.multiply(DAYS_IN_MONTH).multiply(BigDecimal.valueOf(duration.getLength()));
         }
         else if (duration.getUnit().equals(TimeUnit.MONTHS)) {
-            return price.divide(BigDecimal.valueOf(duration.getLength()), SCALE, BigDecimal.ROUND_HALF_UP);
+            return price.divide(BigDecimal.valueOf(duration.getLength()), Rounder.SCALE, BigDecimal.ROUND_HALF_UP);
         }
         else if (duration.getUnit().equals(TimeUnit.YEARS)) {
-            return price.divide(BigDecimal.valueOf(duration.getLength()), SCALE, RoundingMode.HALF_UP).divide(MONTHS_IN_YEAR, SCALE, RoundingMode.HALF_UP);
+            return price.divide(BigDecimal.valueOf(duration.getLength()), Rounder.SCALE, RoundingMode.HALF_UP).divide(MONTHS_IN_YEAR, Rounder.SCALE, RoundingMode.HALF_UP);
         }
         else {
             log.error("Unknown duration [" + duration + "], can't compute mrr");
@@ -239,25 +249,15 @@ public class BusinessSubscription
         }
     }
 
-    public static double round(final BigDecimal decimal)
-    {
-        if (decimal == null) {
-            return 0;
-        }
-        else {
-            return decimal.setScale(SCALE, BigDecimal.ROUND_HALF_UP).doubleValue();
-        }
-    }
-
     @Override
     public String toString()
     {
         final StringBuilder sb = new StringBuilder();
         sb.append("BusinessSubscription");
-        sb.append("{bundleId=").append(bundleId);
+        sb.append("{billingPeriod='").append(billingPeriod).append('\'');
         sb.append(", productName='").append(productName).append('\'');
         sb.append(", productType='").append(productType).append('\'');
-        sb.append(", productCategory='").append(productCategory).append('\'');
+        sb.append(", productCategory=").append(productCategory);
         sb.append(", slug='").append(slug).append('\'');
         sb.append(", phase='").append(phase).append('\'');
         sb.append(", price=").append(price);
@@ -266,6 +266,7 @@ public class BusinessSubscription
         sb.append(", startDate=").append(startDate);
         sb.append(", state=").append(state);
         sb.append(", subscriptionId=").append(subscriptionId);
+        sb.append(", bundleId=").append(bundleId);
         sb.append('}');
         return sb.toString();
     }
@@ -282,19 +283,22 @@ public class BusinessSubscription
 
         final BusinessSubscription that = (BusinessSubscription) o;
 
+        if (billingPeriod != null ? !billingPeriod.equals(that.billingPeriod) : that.billingPeriod != null) {
+            return false;
+        }
         if (bundleId != null ? !bundleId.equals(that.bundleId) : that.bundleId != null) {
             return false;
         }
         if (currency != null ? !currency.equals(that.currency) : that.currency != null) {
             return false;
         }
-        if (mrr != null ? !(round(mrr) == round(that.mrr)) : that.mrr != null) {
+        if (mrr != null ? !(Rounder.round(mrr) == Rounder.round(that.mrr)) : that.mrr != null) {
             return false;
         }
         if (phase != null ? !phase.equals(that.phase) : that.phase != null) {
             return false;
         }
-        if (price != null ? !(round(price) == round(that.price)) : that.price != null) {
+        if (price != null ? !(Rounder.round(price) == Rounder.round(that.price)) : that.price != null) {
             return false;
         }
         if (productCategory != null ? !productCategory.equals(that.productCategory) : that.productCategory != null) {
@@ -337,6 +341,7 @@ public class BusinessSubscription
         result = 31 * result + (state != null ? state.hashCode() : 0);
         result = 31 * result + (subscriptionId != null ? subscriptionId.hashCode() : 0);
         result = 31 * result + (bundleId != null ? bundleId.hashCode() : 0);
+        result = 31 * result + (billingPeriod != null ? billingPeriod.hashCode() : 0);
         return result;
     }
 }
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
new file mode 100644
index 0000000..6a3e218
--- /dev/null
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountBinder.java
@@ -0,0 +1,72 @@
+/*
+ * 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.analytics.dao;
+
+import com.google.common.base.Joiner;
+import com.ning.billing.analytics.BusinessAccount;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@BindingAnnotation(BusinessAccountBinder.BacBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface BusinessAccountBinder
+{
+    public static class BacBinderFactory implements BinderFactory
+    {
+        private final Joiner joiner = Joiner.on(";").skipNulls();
+
+        public Binder build(final Annotation annotation)
+        {
+            return new Binder<BusinessAccountBinder, BusinessAccount>()
+            {
+                public void bind(final SQLStatement q, final BusinessAccountBinder bind, final BusinessAccount account)
+                {
+                    final DateTime dateTimeNow = new DateTime(DateTimeZone.UTC);
+
+                    if (account.getCreatedDt() != null) {
+                        q.bind("created_dt", account.getCreatedDt().getMillis());
+                    }
+                    else {
+                        q.bind("created_dt", dateTimeNow.getMillis());
+                    }
+                    q.bind("updated_dt", dateTimeNow.getMillis());
+
+                    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());
+                    q.bind("total_invoice_balance", account.getRoundedTotalInvoiceBalance());
+                    q.bind("last_payment_status", account.getLastPaymentStatus());
+                    q.bind("payment_method", account.getPaymentMethod());
+                    q.bind("credit_card_type", account.getCreditCardType());
+                    q.bind("billing_address_country", account.getBillingAddressCountry());
+                }
+            };
+        }
+    }
+}
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountDao.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountDao.java
new file mode 100644
index 0000000..7223f82
--- /dev/null
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountDao.java
@@ -0,0 +1,41 @@
+/*
+ * 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.analytics.dao;
+
+import com.ning.billing.analytics.BusinessAccount;
+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.stringtemplate.ExternalizedSqlViaStringTemplate3;
+
+@ExternalizedSqlViaStringTemplate3()
+@RegisterMapper(BusinessAccountMapper.class)
+public interface BusinessAccountDao
+{
+    @SqlQuery
+    BusinessAccount getAccount(@Bind("account_key") final String key);
+
+    @SqlUpdate
+    int createAccount(@BusinessAccountBinder final BusinessAccount account);
+
+    @SqlUpdate
+    int saveAccount(@BusinessAccountBinder final BusinessAccount account);
+
+    @SqlUpdate
+    void test();
+}
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountMapper.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountMapper.java
new file mode 100644
index 0000000..48e22e0
--- /dev/null
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountMapper.java
@@ -0,0 +1,59 @@
+/*
+ * 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.analytics.dao;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+import com.ning.billing.analytics.BusinessAccount;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class BusinessAccountMapper implements ResultSetMapper<BusinessAccount>
+{
+    private final Splitter splitter = Splitter.on(";").trimResults().omitEmptyStrings();
+
+    @Override
+    public BusinessAccount map(final int index, final ResultSet r, final StatementContext ctx) throws SQLException
+    {
+        final List<String> tags = new ArrayList<String>();
+        Iterables.addAll(tags, splitter.split(r.getString(5)));
+
+        final BusinessAccount account = new BusinessAccount(
+            r.getString(1),
+            BigDecimal.valueOf(r.getDouble(4)),
+            tags,
+            new DateTime(r.getLong(6), DateTimeZone.UTC),
+            BigDecimal.valueOf(r.getDouble(7)),
+            r.getString(8),
+            r.getString(9),
+            r.getString(10),
+            r.getString(11)
+        );
+        account.setCreatedDt(new DateTime(r.getLong(2), DateTimeZone.UTC));
+        account.setUpdatedDt(new DateTime(r.getLong(3), DateTimeZone.UTC));
+
+        return account;
+    }
+}
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java
index 6a10741..ccf2a2b 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java
@@ -54,6 +54,7 @@ public @interface BusinessSubscriptionTransitionBinder
                         q.bindNull("prev_product_category", Types.VARCHAR);
                         q.bindNull("prev_slug", Types.VARCHAR);
                         q.bindNull("prev_phase", Types.VARCHAR);
+                        q.bindNull("prev_billing_period", Types.VARCHAR);
                         q.bindNull("prev_price", Types.NUMERIC);
                         q.bindNull("prev_mrr", Types.NUMERIC);
                         q.bindNull("prev_currency", Types.VARCHAR);
@@ -73,6 +74,7 @@ public @interface BusinessSubscriptionTransitionBinder
                         }
                         q.bind("prev_slug", previousSubscription.getSlug());
                         q.bind("prev_phase", previousSubscription.getPhase());
+                        q.bind("prev_billing_period", previousSubscription.getBillingPeriod());
                         q.bind("prev_price", previousSubscription.getRoundedPrice());
                         q.bind("prev_mrr", previousSubscription.getRoundedMrr());
                         q.bind("prev_currency", previousSubscription.getCurrency());
@@ -109,6 +111,7 @@ public @interface BusinessSubscriptionTransitionBinder
                         q.bindNull("next_product_category", Types.VARCHAR);
                         q.bindNull("next_slug", Types.VARCHAR);
                         q.bindNull("next_phase", Types.VARCHAR);
+                        q.bindNull("next_billing_period", Types.VARCHAR);
                         q.bindNull("next_price", Types.NUMERIC);
                         q.bindNull("next_mrr", Types.NUMERIC);
                         q.bindNull("next_currency", Types.VARCHAR);
@@ -128,6 +131,7 @@ public @interface BusinessSubscriptionTransitionBinder
                         }
                         q.bind("next_slug", nextSubscription.getSlug());
                         q.bind("next_phase", nextSubscription.getPhase());
+                        q.bind("next_billing_period", nextSubscription.getBillingPeriod());
                         q.bind("next_price", nextSubscription.getRoundedPrice());
                         q.bind("next_mrr", nextSubscription.getRoundedMrr());
                         q.bind("next_currency", nextSubscription.getCurrency());
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionDaoProvider.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionDaoProvider.java
new file mode 100644
index 0000000..86b5665
--- /dev/null
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionDaoProvider.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.analytics.dao;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import org.skife.jdbi.v2.DBI;
+
+public class BusinessSubscriptionTransitionDaoProvider implements Provider<BusinessSubscriptionTransitionDao>
+{
+    private final DBI dbi;
+
+    @Inject
+    public BusinessSubscriptionTransitionDaoProvider(final DBI dbi)
+    {
+        this.dbi = dbi;
+    }
+
+    @Override
+    public BusinessSubscriptionTransitionDao get()
+    {
+        return dbi.onDemand(BusinessSubscriptionTransitionDao.class);
+    }
+}
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionMapper.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionMapper.java
index 1788193..55aedb9 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionMapper.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionMapper.java
@@ -43,13 +43,14 @@ public class BusinessSubscriptionTransitionMapper implements ResultSetMapper<Bus
             r.getString(6) == null ? null : ProductCategory.valueOf(r.getString(6)), // productCategory
             r.getString(7), // slug
             r.getString(8),  // phase
-            BigDecimal.valueOf(r.getDouble(9)), // price
-            BigDecimal.valueOf(r.getDouble(10)), // mrr
-            r.getString(11), // currency
-            r.getLong(12) == 0 ? null : new DateTime(r.getLong(12), DateTimeZone.UTC), // startDate
-            r.getString(13) == null ? null : SubscriptionState.valueOf(r.getString(13)), // state
-            r.getString(14) == null ? null : UUID.fromString(r.getString(14)), // subscriptionId
-            r.getString(15) == null ? null : UUID.fromString(r.getString(15)) //bundleId
+            r.getString(9),  // billing period
+            BigDecimal.valueOf(r.getDouble(10)), // price
+            BigDecimal.valueOf(r.getDouble(11)), // mrr
+            r.getString(12), // currency
+            r.getLong(13) == 0 ? null : new DateTime(r.getLong(13), DateTimeZone.UTC), // startDate
+            r.getString(14) == null ? null : SubscriptionState.valueOf(r.getString(14)), // state
+            r.getString(15) == null ? null : UUID.fromString(r.getString(15)), // subscriptionId
+            r.getString(16) == null ? null : UUID.fromString(r.getString(16)) //bundleId
         );
 
         // Avoid creating a dummy subscriptions with all null fields
@@ -58,18 +59,19 @@ public class BusinessSubscriptionTransitionMapper implements ResultSetMapper<Bus
         }
 
         BusinessSubscription next = new BusinessSubscription(
-            r.getString(16), // productName
-            r.getString(17), // productType
-            r.getString(18) == null ? null : ProductCategory.valueOf(r.getString(18)), // productCategory
-            r.getString(19), // slug8
-            r.getString(20),  // phase
-            BigDecimal.valueOf(r.getDouble(21)), // price
-            BigDecimal.valueOf(r.getDouble(22)), // mrr
-            r.getString(23), // currency
-            r.getLong(24) == 0 ? null : new DateTime(r.getLong(24), DateTimeZone.UTC), // startDate
-            r.getString(25) == null ? null : SubscriptionState.valueOf(r.getString(25)), // state
-            r.getString(26) == null ? null : UUID.fromString(r.getString(26)), // subscriptionId
-            r.getString(27) == null ? null : UUID.fromString(r.getString(27)) //bundleId
+            r.getString(17), // productName
+            r.getString(18), // productType
+            r.getString(19) == null ? null : ProductCategory.valueOf(r.getString(19)), // productCategory
+            r.getString(20), // slug8
+            r.getString(21),  // phase
+            r.getString(22),  // billing period
+            BigDecimal.valueOf(r.getDouble(23)), // price
+            BigDecimal.valueOf(r.getDouble(24)), // mrr
+            r.getString(25), // currency
+            r.getLong(26) == 0 ? null : new DateTime(r.getLong(26), DateTimeZone.UTC), // startDate
+            r.getString(27) == null ? null : SubscriptionState.valueOf(r.getString(27)), // state
+            r.getString(28) == null ? null : UUID.fromString(r.getString(28)), // subscriptionId
+            r.getString(29) == null ? null : UUID.fromString(r.getString(29)) //bundleId
         );
 
         // Avoid creating a dummy subscriptions with all null fields
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 46e9131..7c74808 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
@@ -18,14 +18,17 @@ package com.ning.billing.analytics.setup;
 
 
 import com.google.inject.AbstractModule;
-import com.ning.billing.analytics.dao.EventDao;
-import com.ning.billing.analytics.dao.EventDaoProvider;
+import com.ning.billing.analytics.dao.BusinessAccountDao;
+import com.ning.billing.analytics.dao.BusinessAccountDaoProvider;
+import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionDao;
+import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionDaoProvider;
 
 public class AnalyticsModule extends AbstractModule
 {
     @Override
     protected void configure()
     {
-        bind(EventDao.class).toProvider(EventDaoProvider.class).asEagerSingleton();
+        bind(BusinessSubscriptionTransitionDao.class).toProvider(BusinessSubscriptionTransitionDaoProvider.class).asEagerSingleton();
+        bind(BusinessAccountDao.class).toProvider(BusinessAccountDaoProvider.class).asEagerSingleton();
     }
 }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/utils/Rounder.java b/analytics/src/main/java/com/ning/billing/analytics/utils/Rounder.java
new file mode 100644
index 0000000..0f0adef
--- /dev/null
+++ b/analytics/src/main/java/com/ning/billing/analytics/utils/Rounder.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.analytics.utils;
+
+import java.math.BigDecimal;
+
+public class Rounder
+{
+    public static final int SCALE = 4;
+
+    public static double round(final BigDecimal decimal)
+    {
+        if (decimal == null) {
+            return 0;
+        }
+        else {
+            return decimal.setScale(SCALE, BigDecimal.ROUND_HALF_UP).doubleValue();
+        }
+    }
+}
diff --git a/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessAccountDao.sql.stg b/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessAccountDao.sql.stg
new file mode 100644
index 0000000..55f03e8
--- /dev/null
+++ b/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessAccountDao.sql.stg
@@ -0,0 +1,67 @@
+group BusinessAccount;
+
+getAccount(account_key) ::= <<
+  select
+    account_key
+  , created_dt
+  , updated_dt
+  , balance
+  , tags
+  , last_invoice_date
+  , total_invoice_balance
+  , last_payment_status
+  , payment_method
+  , credit_card_type
+  , billing_address_country
+  from bac
+  where account_key=:account_key
+  limit 1
+  ;
+>>
+
+createAccount() ::= <<
+  insert into bac(
+    account_key
+  , created_dt
+  , updated_dt
+  , balance
+  , tags
+  , last_invoice_date
+  , total_invoice_balance
+  , last_payment_status
+  , payment_method
+  , credit_card_type
+  , billing_address_country
+  ) values (
+    :account_key
+  , :created_dt
+  , :updated_dt
+  , :balance
+  , :tags
+  , :last_invoice_date
+  , :total_invoice_balance
+  , :last_payment_status
+  , :payment_method
+  , :credit_card_type
+  , :billing_address_country
+  );
+>>
+
+saveAccount() ::= <<
+  update bac set
+    updated_dt=:updated_dt
+  , balance=:balance
+  , tags=:tags
+  , last_invoice_date=:last_invoice_date
+  , total_invoice_balance=:total_invoice_balance
+  , last_payment_status=:last_payment_status
+  , payment_method=:payment_method
+  , credit_card_type=:credit_card_type
+  , billing_address_country=:billing_address_country
+  where account_key=:account_key
+  ;
+>>
+
+test() ::= <<
+  select 1 from bac;
+>>
diff --git a/analytics/src/main/resources/com/ning/billing/analytics/ddl.sql b/analytics/src/main/resources/com/ning/billing/analytics/ddl.sql
index f7be5f4..908715a 100644
--- a/analytics/src/main/resources/com/ning/billing/analytics/ddl.sql
+++ b/analytics/src/main/resources/com/ning/billing/analytics/ddl.sql
@@ -8,6 +8,7 @@ create table bst (
 , prev_product_category varchar(32) default null
 , prev_slug varchar(50) default null
 , prev_phase varchar(32) default null
+, prev_billing_period varchar(32) default null
 , prev_price numeric(10, 4) default 0
 , prev_mrr numeric(10, 4) default 0
 , prev_currency varchar(32) default null
@@ -20,6 +21,7 @@ create table bst (
 , next_product_category varchar(32) default null
 , next_slug varchar(50) default null
 , next_phase varchar(32) default null
+, next_billing_period varchar(32) default null
 , next_price numeric(10, 4) default 0
 , next_mrr numeric(10, 4) default 0
 , next_currency varchar(32) default null
@@ -28,5 +30,20 @@ create table bst (
 , next_subscription_id varchar(100) default null
 , next_bundle_id varchar(100) default null
 ) engine=innodb;
-
 create index bst_key_index on bst (event_key, requested_timestamp asc);
+
+drop table if exists bac;
+create table bac (
+  account_key varchar(50) not null
+, created_dt bigint not null
+, updated_dt bigint not null
+, balance numeric(10, 4) default 0
+, tags varchar(500) default null
+, last_invoice_date bigint default null
+, total_invoice_balance numeric(10, 4) default 0
+, last_payment_status varchar(100) default null
+, payment_method varchar(100) default null
+, credit_card_type varchar(32) default null
+, billing_address_country varchar(100) default null
+) engine=innodb;
+create unique index bac_key_index on bac (account_key);
\ No newline at end of file
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java b/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java
index 54c0773..5075a92 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java
@@ -83,7 +83,7 @@ public class MockPhase implements IPlanPhase
     @Override
     public BillingPeriod getBillingPeriod()
     {
-        throw new UnsupportedOperationException();
+        return BillingPeriod.MONTHLY;
     }
 
     @Override
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessAccount.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessAccount.java
new file mode 100644
index 0000000..5bdba44
--- /dev/null
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessAccount.java
@@ -0,0 +1,47 @@
+/*
+ * 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.analytics;
+
+import org.joda.time.DateTime;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.math.BigDecimal;
+import java.util.Collections;
+
+public class TestBusinessAccount
+{
+    private BusinessAccount account;
+
+    @BeforeMethod(alwaysRun = true)
+    public void setUp() throws Exception
+    {
+        account = new BusinessAccount("pierre", BigDecimal.ONE, Collections.singletonList("batch15"), new DateTime(), BigDecimal.TEN, "ERROR_NOT_ENOUGH_FUNDS", "CreditCard", "Visa", "");
+    }
+
+    @Test(groups = "fast")
+    public void testEquals() throws Exception
+    {
+        Assert.assertSame(account, account);
+        Assert.assertEquals(account, account);
+        Assert.assertTrue(account.equals(account));
+
+        final BusinessAccount otherAccount = new BusinessAccount("pierre", BigDecimal.ONE, Collections.singletonList("batch15"), new DateTime(), BigDecimal.TEN, "ERROR_NOT_ENOUGH_FUNDS", "CreditCard", "Visa", "");
+        Assert.assertFalse(account.equals(otherAccount));
+    }
+}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java
index 2def231..f185a2a 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java
@@ -84,6 +84,7 @@ public class TestBusinessSubscription
         Assert.assertEquals(subscription.getRoundedMrr(), 0.0);
         Assert.assertEquals(subscription.getSlug(), phase.getName());
         Assert.assertEquals(subscription.getPhase(), phase.getPhaseType().toString());
+        Assert.assertEquals(subscription.getBillingPeriod(), phase.getBillingPeriod().toString());
         Assert.assertEquals(subscription.getPrice(), phase.getRecurringPrice().getPrice(null));
         Assert.assertEquals(subscription.getProductCategory(), product.getCategory());
         Assert.assertEquals(subscription.getProductName(), product.getName());