killbill-memoizeit

Merge branch 'integration' of github.com:ning/killbill

7/4/2012 4:54:19 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 1a2585b..af8c7c6 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
@@ -27,6 +27,7 @@ import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 import com.ning.billing.entitlement.api.user.RequestedSubscriptionEvent;
 import com.ning.billing.invoice.api.EmptyInvoiceEvent;
 import com.ning.billing.invoice.api.InvoiceCreationEvent;
+import com.ning.billing.overdue.OverdueChangeEvent;
 import com.ning.billing.payment.api.PaymentErrorEvent;
 import com.ning.billing.payment.api.PaymentInfoEvent;
 import com.ning.billing.util.tag.api.ControlTagCreationEvent;
@@ -42,16 +43,19 @@ public class AnalyticsListener {
     private final BusinessSubscriptionTransitionRecorder bstRecorder;
     private final BusinessAccountRecorder bacRecorder;
     private final BusinessInvoiceRecorder invoiceRecorder;
+    private final BusinessOverdueStatusRecorder bosRecorder;
     private final BusinessTagRecorder tagRecorder;
 
     @Inject
     public AnalyticsListener(final BusinessSubscriptionTransitionRecorder bstRecorder,
                              final BusinessAccountRecorder bacRecorder,
                              final BusinessInvoiceRecorder invoiceRecorder,
+                             final BusinessOverdueStatusRecorder bosRecorder,
                              final BusinessTagRecorder tagRecorder) {
         this.bstRecorder = bstRecorder;
         this.bacRecorder = bacRecorder;
         this.invoiceRecorder = invoiceRecorder;
+        this.bosRecorder = bosRecorder;
         this.tagRecorder = tagRecorder;
     }
 
@@ -109,6 +113,11 @@ public class AnalyticsListener {
     }
 
     @Subscribe
+    public void handleOverdueChange(final OverdueChangeEvent changeEvent) {
+        bosRecorder.overdueStatusChanged(changeEvent.getOverdueObjectType(), changeEvent.getOverdueObjectId());
+    }
+
+    @Subscribe
     public void handleControlTagCreation(final ControlTagCreationEvent event) {
         tagRecorder.tagAdded(event.getObjectType(), event.getObjectId(), event.getTagDefinition().getName());
     }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/api/user/DefaultAnalyticsUserApi.java b/analytics/src/main/java/com/ning/billing/analytics/api/user/DefaultAnalyticsUserApi.java
index 424e1c2..102fb95 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/api/user/DefaultAnalyticsUserApi.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/api/user/DefaultAnalyticsUserApi.java
@@ -25,6 +25,7 @@ import com.ning.billing.analytics.model.BusinessAccount;
 import com.ning.billing.analytics.model.BusinessAccountTag;
 import com.ning.billing.analytics.model.BusinessInvoice;
 import com.ning.billing.analytics.model.BusinessInvoiceItem;
+import com.ning.billing.analytics.model.BusinessOverdueStatus;
 import com.ning.billing.analytics.model.BusinessSubscriptionTransition;
 
 // Note: not exposed in api yet
@@ -52,6 +53,10 @@ public class DefaultAnalyticsUserApi {
         return analyticsDao.getTagsForAccount(accountKey);
     }
 
+    public List<BusinessOverdueStatus> getOverdueStatusesForBundle(final String externalKey) {
+        return analyticsDao.getOverdueStatusesForBundleByKey(externalKey);
+    }
+
     public List<BusinessInvoiceItem> getInvoiceItemsForInvoice(final UUID invoiceId) {
         return analyticsDao.getInvoiceItemsForInvoice(invoiceId.toString());
     }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessOverdueStatusRecorder.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessOverdueStatusRecorder.java
new file mode 100644
index 0000000..8ec98d4
--- /dev/null
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessOverdueStatusRecorder.java
@@ -0,0 +1,116 @@
+/*
+ * 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.List;
+import java.util.SortedSet;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.Transaction;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+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.BusinessOverdueStatusSqlDao;
+import com.ning.billing.analytics.model.BusinessOverdueStatus;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.api.BlockingState;
+
+public class BusinessOverdueStatusRecorder {
+    private static final Logger log = LoggerFactory.getLogger(BusinessOverdueStatusRecorder.class);
+
+    private final BusinessOverdueStatusSqlDao overdueStatusSqlDao;
+    private final AccountUserApi accountApi;
+    private final EntitlementUserApi entitlementApi;
+    private final BlockingApi blockingApi;
+
+    @Inject
+    public BusinessOverdueStatusRecorder(final BusinessOverdueStatusSqlDao overdueStatusSqlDao, final AccountUserApi accountApi,
+                                         final EntitlementUserApi entitlementApi, final BlockingApi blockingApi) {
+        this.overdueStatusSqlDao = overdueStatusSqlDao;
+        this.accountApi = accountApi;
+        this.entitlementApi = entitlementApi;
+        this.blockingApi = blockingApi;
+    }
+
+    public void overdueStatusChanged(final Blockable.Type objectType, final UUID objectId) {
+        if (Blockable.Type.SUBSCRIPTION_BUNDLE.equals(objectType)) {
+            overdueStatusChangedForBundle(objectId);
+        } else {
+            log.info("Ignoring overdue status change for object id {} (type {})", objectId.toString(), objectType.toString());
+        }
+    }
+
+    private void overdueStatusChangedForBundle(final UUID bundleId) {
+        final SubscriptionBundle bundle;
+        try {
+            bundle = entitlementApi.getBundleFromId(bundleId);
+        } catch (EntitlementUserApiException e) {
+            log.warn("Ignoring update for bundle {}: bundle does not exist", bundleId);
+            return;
+        }
+
+        final Account account;
+        try {
+            account = accountApi.getAccountById(bundle.getAccountId());
+        } catch (AccountApiException e) {
+            log.warn("Ignoring update for bundle {}: account {} does not exist", bundleId, bundle.getAccountId());
+            return;
+        }
+
+        final String accountKey = account.getExternalKey();
+        final String externalKey = bundle.getKey();
+
+        overdueStatusSqlDao.inTransaction(new Transaction<Void, BusinessOverdueStatusSqlDao>() {
+            @Override
+            public Void inTransaction(final BusinessOverdueStatusSqlDao transactional, final TransactionStatus status) throws Exception {
+                log.info("Started rebuilding overdue statuses for bundle id {}", bundleId);
+                transactional.deleteOverdueStatusesForBundle(bundleId.toString());
+
+                final SortedSet<BlockingState> blockingHistory = blockingApi.getBlockingHistory(bundleId);
+                if (blockingHistory != null && blockingHistory.size() > 0) {
+                    final List<BlockingState> overdueStates = ImmutableList.<BlockingState>copyOf(blockingHistory);
+                    final List<BlockingState> overdueStatesReversed = Lists.reverse(overdueStates);
+
+                    DateTime previousStartDate = null;
+                    for (final BlockingState state : overdueStatesReversed) {
+                        final BusinessOverdueStatus overdueStatus = new BusinessOverdueStatus(accountKey, bundleId, previousStartDate,
+                                                                                              externalKey, state.getTimestamp(), state.getStateName());
+                        log.info("Adding overdue state {}", overdueStatus);
+                        overdueStatusSqlDao.createOverdueStatus(overdueStatus);
+
+                        previousStartDate = state.getTimestamp();
+                    }
+                }
+
+                log.info("Finished rebuilding overdue statuses for bundle id {}", bundleId);
+                return null;
+            }
+        });
+    }
+}
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/AnalyticsDao.java b/analytics/src/main/java/com/ning/billing/analytics/dao/AnalyticsDao.java
index 2f0b6db..97d72f2 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/AnalyticsDao.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/AnalyticsDao.java
@@ -22,6 +22,7 @@ import com.ning.billing.analytics.model.BusinessAccount;
 import com.ning.billing.analytics.model.BusinessAccountTag;
 import com.ning.billing.analytics.model.BusinessInvoice;
 import com.ning.billing.analytics.model.BusinessInvoiceItem;
+import com.ning.billing.analytics.model.BusinessOverdueStatus;
 import com.ning.billing.analytics.model.BusinessSubscriptionTransition;
 
 public interface AnalyticsDao {
@@ -34,4 +35,6 @@ public interface AnalyticsDao {
     List<BusinessAccountTag> getTagsForAccount(final String accountKey);
 
     List<BusinessInvoiceItem> getInvoiceItemsForInvoice(final String invoiceId);
+
+    List<BusinessOverdueStatus> getOverdueStatusesForBundleByKey(final String externalKey);
 }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessOverdueStatusSqlDao.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessOverdueStatusSqlDao.java
index 44668d0..b910919 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessOverdueStatusSqlDao.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessOverdueStatusSqlDao.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.BusinessOverdueStatus;
 
 @ExternalizedSqlViaStringTemplate3()
 @RegisterMapper(BusinessOverdueStatusMapper.class)
-public interface BusinessOverdueStatusSqlDao {
+public interface BusinessOverdueStatusSqlDao extends Transactional<BusinessOverdueStatusSqlDao>, Transmogrifier {
     @SqlQuery
     List<BusinessOverdueStatus> getOverdueStatusesForBundle(@Bind("external_key") final String externalKey);
 
@@ -36,5 +38,8 @@ public interface BusinessOverdueStatusSqlDao {
     int createOverdueStatus(@BusinessOverdueStatusBinder final BusinessOverdueStatus status);
 
     @SqlUpdate
+    void deleteOverdueStatusesForBundle(@Bind("bundle_id") final String bundleId);
+
+    @SqlUpdate
     void test();
 }
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
index edbf529..5ab5dbb 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/DefaultAnalyticsDao.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/DefaultAnalyticsDao.java
@@ -23,6 +23,7 @@ import com.ning.billing.analytics.model.BusinessAccount;
 import com.ning.billing.analytics.model.BusinessAccountTag;
 import com.ning.billing.analytics.model.BusinessInvoice;
 import com.ning.billing.analytics.model.BusinessInvoiceItem;
+import com.ning.billing.analytics.model.BusinessOverdueStatus;
 import com.ning.billing.analytics.model.BusinessSubscriptionTransition;
 
 public class DefaultAnalyticsDao implements AnalyticsDao {
@@ -31,18 +32,21 @@ public class DefaultAnalyticsDao implements AnalyticsDao {
     private final BusinessInvoiceSqlDao invoiceSqlDao;
     private final BusinessInvoiceItemSqlDao invoiceItemSqlDao;
     private final BusinessAccountTagSqlDao accountTagSqlDao;
+    private final BusinessOverdueStatusSqlDao overdueStatusSqlDao;
 
     @Inject
     public DefaultAnalyticsDao(final BusinessAccountSqlDao accountSqlDao,
                                final BusinessSubscriptionTransitionSqlDao subscriptionTransitionSqlDao,
                                final BusinessInvoiceSqlDao invoiceSqlDao,
                                final BusinessInvoiceItemSqlDao invoiceItemSqlDao,
-                               final BusinessAccountTagSqlDao accountTagSqlDao) {
+                               final BusinessAccountTagSqlDao accountTagSqlDao,
+                               final BusinessOverdueStatusSqlDao overdueStatusSqlDao) {
         this.accountSqlDao = accountSqlDao;
         this.subscriptionTransitionSqlDao = subscriptionTransitionSqlDao;
         this.invoiceSqlDao = invoiceSqlDao;
         this.invoiceItemSqlDao = invoiceItemSqlDao;
         this.accountTagSqlDao = accountTagSqlDao;
+        this.overdueStatusSqlDao = overdueStatusSqlDao;
     }
 
     @Override
@@ -69,4 +73,9 @@ public class DefaultAnalyticsDao implements AnalyticsDao {
     public List<BusinessInvoiceItem> getInvoiceItemsForInvoice(final String invoiceId) {
         return invoiceItemSqlDao.getInvoiceItemsForInvoice(invoiceId);
     }
+
+    @Override
+    public List<BusinessOverdueStatus> getOverdueStatusesForBundleByKey(final String externalKey) {
+        return overdueStatusSqlDao.getOverdueStatusesForBundle(externalKey);
+    }
 }
diff --git a/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessOverdueStatusSqlDao.sql.stg b/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessOverdueStatusSqlDao.sql.stg
index a785959..fddafdb 100644
--- a/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessOverdueStatusSqlDao.sql.stg
+++ b/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessOverdueStatusSqlDao.sql.stg
@@ -32,6 +32,10 @@ insert into bos (
 );
 >>
 
+deleteOverdueStatusesForBundle(bundle_id) ::= <<
+delete from bos where bundle_id = :bundle_id;
+>>
+
 test() ::= <<
 select 1 from bos;
 >>
diff --git a/api/src/main/java/com/ning/billing/overdue/OverdueChangeEvent.java b/api/src/main/java/com/ning/billing/overdue/OverdueChangeEvent.java
index 5b6aa57..daa731d 100644
--- a/api/src/main/java/com/ning/billing/overdue/OverdueChangeEvent.java
+++ b/api/src/main/java/com/ning/billing/overdue/OverdueChangeEvent.java
@@ -22,12 +22,11 @@ import com.ning.billing.junction.api.Blockable;
 import com.ning.billing.util.bus.BusEvent;
 
 public interface OverdueChangeEvent extends BusEvent {
-
     UUID getOverdueObjectId();
-    
+
     Blockable.Type getOverdueObjectType();
-    
+
     String getPreviousOverdueStateName();
-    
+
     String getNextOverdueStateName();
 }
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestAnalytics.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestAnalytics.java
index 9d4b29a..a9b9e76 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestAnalytics.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestAnalytics.java
@@ -16,6 +16,8 @@
 
 package com.ning.billing.beatrix.integration;
 
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
@@ -50,8 +52,10 @@ import com.ning.billing.catalog.api.ProductCategory;
 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.overdue.config.OverdueConfig;
 import com.ning.billing.util.api.TagApiException;
 import com.ning.billing.util.api.TagDefinitionApiException;
+import com.ning.billing.util.config.XMLLoader;
 import com.ning.billing.util.dao.ObjectType;
 import com.ning.billing.util.tag.TagDefinition;
 
@@ -63,6 +67,53 @@ public class TestAnalytics extends TestIntegrationBase {
 
     @BeforeMethod(groups = "slow")
     public void setUpAnalyticsHandler() throws Exception {
+        final String configXml = "<overdueConfig>" +
+                "   <bundleOverdueStates>" +
+                "       <state name=\"OD3\">" +
+                "           <condition>" +
+                "               <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                "                   <unit>DAYS</unit><number>50</number>" +
+                "               </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                "           </condition>" +
+                "           <externalMessage>Reached OD3</externalMessage>" +
+                "           <blockChanges>true</blockChanges>" +
+                "           <disableEntitlementAndChangesBlocked>true</disableEntitlementAndChangesBlocked>" +
+                "           <autoReevaluationInterval>" +
+                "               <unit>DAYS</unit><number>5</number>" +
+                "           </autoReevaluationInterval>" +
+                "       </state>" +
+                "       <state name=\"OD2\">" +
+                "           <condition>" +
+                "               <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                "                   <unit>DAYS</unit><number>40</number>" +
+                "               </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                "           </condition>" +
+                "           <externalMessage>Reached OD2</externalMessage>" +
+                "           <blockChanges>true</blockChanges>" +
+                "           <disableEntitlementAndChangesBlocked>true</disableEntitlementAndChangesBlocked>" +
+                "           <autoReevaluationInterval>" +
+                "               <unit>DAYS</unit><number>5</number>" +
+                "           </autoReevaluationInterval>" +
+                "       </state>" +
+                "       <state name=\"OD1\">" +
+                "           <condition>" +
+                "               <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                "                   <unit>DAYS</unit><number>30</number>" +
+                "               </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                "           </condition>" +
+                "           <externalMessage>Reached OD1</externalMessage>" +
+                "           <blockChanges>true</blockChanges>" +
+                "           <disableEntitlementAndChangesBlocked>false</disableEntitlementAndChangesBlocked>" +
+                "           <autoReevaluationInterval>" +
+                "               <unit>DAYS</unit><number>100</number>" + // this number is intentionally too high
+                "           </autoReevaluationInterval>" +
+                "       </state>" +
+                "   </bundleOverdueStates>" +
+                "</overdueConfig>";
+        final InputStream is = new ByteArrayInputStream(configXml.getBytes());
+        final OverdueConfig config = XMLLoader.getObjectFromStreamNoValidation(is, OverdueConfig.class);
+        overdueWrapperFactory.setOverdueConfig(config);
+
         busService.getBus().register(analyticsListener);
     }
 
@@ -131,6 +182,105 @@ public class TestAnalytics extends TestIntegrationBase {
         verifyChangePlan(account, bundle, subscription);
     }
 
+    @Test(groups = "slow")
+    public void testOverdue() throws Exception {
+        paymentPlugin.makeAllInvoicesFailWithError(true);
+
+        // Create an account
+        final Account account = verifyAccountCreation();
+
+        // Create a bundle
+        final SubscriptionBundle bundle = verifyFirstBundle(account);
+
+        // Add a subscription
+        busHandler.pushExpectedEvents(TestApiListener.NextEvent.CREATE, TestApiListener.NextEvent.INVOICE);
+        final Subscription subscription = verifyFirstSubscription(account, bundle);
+        assertTrue(busHandler.isCompleted(DELAY));
+
+        // Verify the initial overdue status
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).size(), 0);
+
+        // Move after trial
+        busHandler.pushExpectedEvents(TestApiListener.NextEvent.PHASE, TestApiListener.NextEvent.INVOICE, TestApiListener.NextEvent.PAYMENT_ERROR);
+        clock.addDays(30); // DAY 30 have to get out of trial before first payment
+        Assert.assertTrue(busHandler.isCompleted(DELAY));
+
+        // Check BST - nothing should have changed
+        verifyBSTWithTrialAndEvergreenPhases(account, bundle, subscription);
+
+        // Verify overdue status - we should still be in clear state
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).size(), 0);
+
+        clock.addDays(15); // DAY 45 - 15 days after invoice
+        assertTrue(busHandler.isCompleted(DELAY));
+
+        // Check BST - nothing should have changed
+        verifyBSTWithTrialAndEvergreenPhases(account, bundle, subscription);
+
+        // Verify overdue status - we should still be in clear state
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).size(), 0);
+
+        busHandler.pushExpectedEvents(TestApiListener.NextEvent.INVOICE, TestApiListener.NextEvent.PAYMENT_ERROR);
+        clock.addDays(20); // DAY 65 - 35 days after invoice
+        assertTrue(busHandler.isCompleted(DELAY));
+        waitALittle();
+
+        // Verify overdue status - we should be in OD1
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).size(), 1);
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(0).getStatus(), "OD1");
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(0).getBundleId(), bundle.getId());
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(0).getExternalKey(), bundle.getKey());
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(0).getAccountKey(), account.getExternalKey());
+
+        clock.addDays(2); // DAY 67 - 37 days after invoice
+        assertTrue(busHandler.isCompleted(DELAY));
+        waitALittle();
+        // Verify overdue status - we should still be in OD1
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).size(), 1);
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(0).getStatus(), "OD1");
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(0).getBundleId(), bundle.getId());
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(0).getExternalKey(), bundle.getKey());
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(0).getAccountKey(), account.getExternalKey());
+
+        clock.addDays(8); // DAY 75 - 45 days after invoice
+        assertTrue(busHandler.isCompleted(DELAY));
+        waitALittle();
+        // Verify overdue status - we should be in OD2
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).size(), 2);
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(0).getStatus(), "OD1");
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(1).getStatus(), "OD2");
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(0).getEndDate(),
+                            analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(1).getStartDate());
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(0).getBundleId(), bundle.getId());
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(0).getExternalKey(), bundle.getKey());
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(0).getAccountKey(), account.getExternalKey());
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(1).getBundleId(), bundle.getId());
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(1).getExternalKey(), bundle.getKey());
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(1).getAccountKey(), account.getExternalKey());
+
+        clock.addDays(10); // DAY 85 - 55 days after invoice
+        assertTrue(busHandler.isCompleted(DELAY));
+        waitALittle();
+        // Verify overdue status - we should be in OD3
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).size(), 3);
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(0).getStatus(), "OD1");
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(1).getStatus(), "OD2");
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(2).getStatus(), "OD3");
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(0).getEndDate(),
+                            analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(1).getStartDate());
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(1).getEndDate(),
+                            analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(2).getStartDate());
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(0).getBundleId(), bundle.getId());
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(0).getExternalKey(), bundle.getKey());
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(0).getAccountKey(), account.getExternalKey());
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(1).getBundleId(), bundle.getId());
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(1).getExternalKey(), bundle.getKey());
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(1).getAccountKey(), account.getExternalKey());
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(2).getBundleId(), bundle.getId());
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(2).getExternalKey(), bundle.getKey());
+        Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(2).getAccountKey(), account.getExternalKey());
+    }
+
     private Account verifyAccountCreation() throws Exception {
         final AccountData accountData = getAccountData(1);
 
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
index fbf4a68..7aeaa80 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
@@ -33,6 +33,7 @@ import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 
 import com.google.inject.Inject;
+import com.google.inject.name.Named;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountData;
 import com.ning.billing.account.api.AccountService;
@@ -56,8 +57,10 @@ import com.ning.billing.invoice.api.InvoiceService;
 import com.ning.billing.invoice.api.InvoiceUserApi;
 import com.ning.billing.invoice.model.InvoicingConfiguration;
 import com.ning.billing.junction.plumbing.api.BlockingSubscription;
+import com.ning.billing.overdue.wrapper.OverdueWrapperFactory;
 import com.ning.billing.payment.api.PaymentApi;
 import com.ning.billing.payment.api.PaymentMethodPlugin;
+import com.ning.billing.payment.provider.MockPaymentProviderPlugin;
 import com.ning.billing.util.api.TagUserApi;
 import com.ning.billing.util.bus.BusService;
 import com.ning.billing.util.callcontext.CallContext;
@@ -124,6 +127,13 @@ public class TestIntegrationBase implements TestListenerStatus {
     @Inject
     protected PaymentApi paymentApi;
 
+    @Named(BeatrixModule.PLUGIN_NAME)
+    @Inject
+    protected MockPaymentProviderPlugin paymentPlugin;
+
+    @Inject
+    protected OverdueWrapperFactory overdueWrapperFactory;
+
     @Inject
     protected AccountUserApi accountUserApi;