killbill-aplcache

server: add first draft of overdue endpoint I'm not sure yet

8/30/2012 7:20:05 PM

Details

diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/OverdueStateJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/OverdueStateJson.java
new file mode 100644
index 0000000..1198646
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/OverdueStateJson.java
@@ -0,0 +1,165 @@
+/*
+ * 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.jaxrs.json;
+
+import org.joda.time.Period;
+
+import com.ning.billing.overdue.OverdueApiException;
+import com.ning.billing.overdue.OverdueState;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class OverdueStateJson {
+
+    private final String name;
+    private final String externalMessage;
+    private final Integer daysBetweenPaymentRetries;
+    private final Boolean disableEntitlementAndChangesBlocked;
+    private final Boolean blockChanges;
+    private final Boolean isClearState;
+    private final Integer reevaluationIntervalDays;
+
+    @JsonCreator
+    public OverdueStateJson(@JsonProperty("name") final String name,
+                            @JsonProperty("externalMessage") final String externalMessage,
+                            @JsonProperty("daysBetweenPaymentRetries") final Integer daysBetweenPaymentRetries,
+                            @JsonProperty("disableEntitlementAndChangesBlocked") final Boolean disableEntitlementAndChangesBlocked,
+                            @JsonProperty("blockChanges") final Boolean blockChanges,
+                            @JsonProperty("clearState") final Boolean isClearState,
+                            @JsonProperty("reevaluationIntervalDays") final Integer reevaluationIntervalDays) {
+        this.name = name;
+        this.externalMessage = externalMessage;
+        this.daysBetweenPaymentRetries = daysBetweenPaymentRetries;
+        this.disableEntitlementAndChangesBlocked = disableEntitlementAndChangesBlocked;
+        this.blockChanges = blockChanges;
+        this.isClearState = isClearState;
+        this.reevaluationIntervalDays = reevaluationIntervalDays;
+    }
+
+    public OverdueStateJson(final OverdueState overdueState) {
+        this.name = overdueState.getName();
+        this.externalMessage = overdueState.getExternalMessage();
+        this.daysBetweenPaymentRetries = overdueState.getDaysBetweenPaymentRetries();
+        this.disableEntitlementAndChangesBlocked = overdueState.disableEntitlementAndChangesBlocked();
+        this.blockChanges = overdueState.blockChanges();
+        this.isClearState = overdueState.isClearState();
+
+        Period reevaluationIntervalPeriod = null;
+        try {
+            reevaluationIntervalPeriod = overdueState.getReevaluationInterval();
+        } catch (OverdueApiException ignored) {
+        }
+
+        if (reevaluationIntervalPeriod != null) {
+            this.reevaluationIntervalDays = reevaluationIntervalPeriod.getDays();
+        } else {
+            this.reevaluationIntervalDays = null;
+        }
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getExternalMessage() {
+        return externalMessage;
+    }
+
+    public Integer getDaysBetweenPaymentRetries() {
+        return daysBetweenPaymentRetries;
+    }
+
+    public Boolean isDisableEntitlementAndChangesBlocked() {
+        return disableEntitlementAndChangesBlocked;
+    }
+
+    public Boolean isBlockChanges() {
+        return blockChanges;
+    }
+
+    public Boolean isClearState() {
+        return isClearState;
+    }
+
+    public Integer getReevaluationIntervalDays() {
+        return reevaluationIntervalDays;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("OverdueStateJson");
+        sb.append("{name='").append(name).append('\'');
+        sb.append(", externalMessage='").append(externalMessage).append('\'');
+        sb.append(", daysBetweenPaymentRetries=").append(daysBetweenPaymentRetries);
+        sb.append(", disableEntitlementAndChangesBlocked=").append(disableEntitlementAndChangesBlocked);
+        sb.append(", blockChanges=").append(blockChanges);
+        sb.append(", isClearState=").append(isClearState);
+        sb.append(", reevaluationIntervalDays=").append(reevaluationIntervalDays);
+        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 OverdueStateJson that = (OverdueStateJson) o;
+
+        if (blockChanges != null ? !blockChanges.equals(that.blockChanges) : that.blockChanges != null) {
+            return false;
+        }
+        if (daysBetweenPaymentRetries != null ? !daysBetweenPaymentRetries.equals(that.daysBetweenPaymentRetries) : that.daysBetweenPaymentRetries != null) {
+            return false;
+        }
+        if (disableEntitlementAndChangesBlocked != null ? !disableEntitlementAndChangesBlocked.equals(that.disableEntitlementAndChangesBlocked) : that.disableEntitlementAndChangesBlocked != null) {
+            return false;
+        }
+        if (externalMessage != null ? !externalMessage.equals(that.externalMessage) : that.externalMessage != null) {
+            return false;
+        }
+        if (isClearState != null ? !isClearState.equals(that.isClearState) : that.isClearState != null) {
+            return false;
+        }
+        if (name != null ? !name.equals(that.name) : that.name != null) {
+            return false;
+        }
+        if (reevaluationIntervalDays != null ? !reevaluationIntervalDays.equals(that.reevaluationIntervalDays) : that.reevaluationIntervalDays != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = name != null ? name.hashCode() : 0;
+        result = 31 * result + (externalMessage != null ? externalMessage.hashCode() : 0);
+        result = 31 * result + (daysBetweenPaymentRetries != null ? daysBetweenPaymentRetries.hashCode() : 0);
+        result = 31 * result + (disableEntitlementAndChangesBlocked != null ? disableEntitlementAndChangesBlocked.hashCode() : 0);
+        result = 31 * result + (blockChanges != null ? blockChanges.hashCode() : 0);
+        result = 31 * result + (isClearState != null ? isClearState.hashCode() : 0);
+        result = 31 * result + (reevaluationIntervalDays != null ? reevaluationIntervalDays.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
index 6e83cc8..56f7140 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
@@ -119,4 +119,6 @@ public interface JaxrsResource {
     public static final String CATALOG = "catalog";
     public static final String CATALOG_PATH = PREFIX + "/" + CATALOG;
 
+    public static final String OVERDUE = "overdue";
+    public static final String OVERDUE_PATH = PREFIX + "/" + OVERDUE;
 }
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/OverdueResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/OverdueResource.java
new file mode 100644
index 0000000..b4c6f64
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/OverdueResource.java
@@ -0,0 +1,92 @@
+/*
+ * 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.jaxrs.resources;
+
+import java.util.UUID;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+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.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.jaxrs.json.OverdueStateJson;
+import com.ning.billing.overdue.OverdueApiException;
+import com.ning.billing.overdue.OverdueState;
+import com.ning.billing.overdue.OverdueUserApi;
+import com.ning.billing.overdue.config.api.OverdueError;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+
+@Singleton
+@Path(JaxrsResource.OVERDUE_PATH)
+public class OverdueResource implements JaxrsResource {
+
+    private final OverdueUserApi overdueApi;
+    private final AccountUserApi accountApi;
+    private final EntitlementUserApi entitlementApi;
+
+    @Inject
+    public OverdueResource(final OverdueUserApi overdueApi,
+                           final AccountUserApi accountApi,
+                           final EntitlementUserApi entitlementApi) {
+        this.overdueApi = overdueApi;
+        this.accountApi = accountApi;
+        this.entitlementApi = entitlementApi;
+    }
+
+    @GET
+    @Path("/" + ACCOUNTS + "/{accountId:" + UUID_PATTERN + "}")
+    @Produces(APPLICATION_JSON)
+    public Response getOverdueAccount(@PathParam("accountId") final String accountId) throws AccountApiException, OverdueError, OverdueApiException {
+        final Account account = accountApi.getAccountById(UUID.fromString(accountId));
+        final OverdueState<Account> overdueState = overdueApi.getOverdueStateFor(account);
+
+        return Response.status(Status.OK).entity(new OverdueStateJson(overdueState)).build();
+    }
+
+    @GET
+    @Path("/" + BUNDLES + "/{bundleId:" + UUID_PATTERN + "}")
+    @Produces(APPLICATION_JSON)
+    public Response getOverdueBundle(@PathParam("bundleId") final String bundleId) throws EntitlementUserApiException, OverdueError, OverdueApiException {
+        final SubscriptionBundle bundle = entitlementApi.getBundleFromId(UUID.fromString(bundleId));
+        final OverdueState<SubscriptionBundle> overdueState = overdueApi.getOverdueStateFor(bundle);
+
+        return Response.status(Status.OK).entity(new OverdueStateJson(overdueState)).build();
+    }
+
+    @GET
+    @Path("/" + SUBSCRIPTIONS + "/{subscriptionId:" + UUID_PATTERN + "}")
+    @Produces(APPLICATION_JSON)
+    public Response getOverdueSubscription(@PathParam("subscriptionId") final String subscriptionId) throws EntitlementUserApiException, OverdueError, OverdueApiException {
+        final Subscription subscription = entitlementApi.getSubscriptionFromId(UUID.fromString(subscriptionId));
+        final OverdueState<Subscription> overdueState = overdueApi.getOverdueStateFor(subscription);
+
+        return Response.status(Status.OK).entity(new OverdueStateJson(overdueState)).build();
+    }
+}
diff --git a/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestOverdueStateJson.java b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestOverdueStateJson.java
new file mode 100644
index 0000000..4dced72
--- /dev/null
+++ b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestOverdueStateJson.java
@@ -0,0 +1,52 @@
+/*
+ * 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.jaxrs.json;
+
+import java.util.UUID;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.jaxrs.JaxrsTestSuite;
+
+public class TestOverdueStateJson extends JaxrsTestSuite {
+
+    @Test(groups = "fast")
+    public void testJson() throws Exception {
+        final String name = UUID.randomUUID().toString();
+        final String externalMessage = UUID.randomUUID().toString();
+        final int daysBetweenPaymentRetries = 12;
+        final boolean disableEntitlementAndChangesBlocked = true;
+        final boolean blockChanges = false;
+        final boolean clearState = true;
+        final int reevaluationIntervalDays = 100;
+        final OverdueStateJson overdueStateJson = new OverdueStateJson(name, externalMessage, daysBetweenPaymentRetries,
+                                                                       disableEntitlementAndChangesBlocked, blockChanges, clearState,
+                                                                       reevaluationIntervalDays);
+        Assert.assertEquals(overdueStateJson.getName(), name);
+        Assert.assertEquals(overdueStateJson.getExternalMessage(), externalMessage);
+        Assert.assertEquals(overdueStateJson.getDaysBetweenPaymentRetries(), (Integer) daysBetweenPaymentRetries);
+        Assert.assertEquals(overdueStateJson.isDisableEntitlementAndChangesBlocked(), (Boolean) disableEntitlementAndChangesBlocked);
+        Assert.assertEquals(overdueStateJson.isBlockChanges(), (Boolean) blockChanges);
+        Assert.assertEquals(overdueStateJson.isClearState(), (Boolean) clearState);
+        Assert.assertEquals(overdueStateJson.getReevaluationIntervalDays(), (Integer) reevaluationIntervalDays);
+
+        final String asJson = mapper.writeValueAsString(overdueStateJson);
+        final OverdueStateJson fromJson = mapper.readValue(asJson, OverdueStateJson.class);
+        Assert.assertEquals(fromJson, overdueStateJson);
+    }
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/service/DefaultOverdueService.java b/overdue/src/main/java/com/ning/billing/overdue/service/DefaultOverdueService.java
index 3df1273..acce124 100644
--- a/overdue/src/main/java/com/ning/billing/overdue/service/DefaultOverdueService.java
+++ b/overdue/src/main/java/com/ning/billing/overdue/service/DefaultOverdueService.java
@@ -85,9 +85,13 @@ public class DefaultOverdueService implements ExtendedOverdueService {
     public synchronized void loadConfig() throws ServiceException {
         if (!isInitialized) {
             try {
-                System.out.println("Overdue config URI" + properties.getConfigURI());
                 final URI u = new URI(properties.getConfigURI());
                 overdueConfig = XMLLoader.getObjectFromUri(u, OverdueConfig.class);
+                // File not found?
+                if (overdueConfig == null) {
+                    log.warn("Unable to load the overdue config from " + properties.getConfigURI());
+                    overdueConfig = new OverdueConfig();
+                }
 
                 isInitialized = true;
             } catch (URISyntaxException e) {

server/pom.xml 4(+4 -0)

diff --git a/server/pom.xml b/server/pom.xml
index d1311cd..06bce5a 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -192,6 +192,10 @@
             <artifactId>killbill-junction</artifactId>
         </dependency>
         <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-overdue</artifactId>
+        </dependency>
+        <dependency>
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
             <version>11.0.2</version>
diff --git a/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java b/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java
index 32ff962..16ab8a6 100644
--- a/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java
+++ b/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java
@@ -38,6 +38,7 @@ import com.ning.billing.jaxrs.resources.SubscriptionResource;
 import com.ning.billing.jaxrs.resources.TagResource;
 import com.ning.billing.jaxrs.util.KillbillEventHandler;
 import com.ning.billing.junction.glue.DefaultJunctionModule;
+import com.ning.billing.overdue.glue.DefaultOverdueModule;
 import com.ning.billing.payment.glue.PaymentModule;
 import com.ning.billing.util.email.EmailModule;
 import com.ning.billing.util.email.templates.TemplateModule;
@@ -105,6 +106,7 @@ public class KillbillServerModule extends AbstractModule {
         install(new PaymentModule());
         install(new BeatrixModule());
         install(new DefaultJunctionModule());
+        install(new DefaultOverdueModule());
         installClock();
     }
 }
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
index f1ae5b0..75e6092 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
@@ -75,6 +75,7 @@ import com.ning.billing.jaxrs.json.CreditJson;
 import com.ning.billing.jaxrs.json.InvoiceItemJsonSimple;
 import com.ning.billing.jaxrs.json.InvoiceJsonSimple;
 import com.ning.billing.jaxrs.json.InvoiceJsonWithItems;
+import com.ning.billing.jaxrs.json.OverdueStateJson;
 import com.ning.billing.jaxrs.json.PaymentJsonSimple;
 import com.ning.billing.jaxrs.json.PaymentJsonWithBundleKeys;
 import com.ning.billing.jaxrs.json.PaymentMethodJson;
@@ -84,6 +85,7 @@ import com.ning.billing.jaxrs.json.RefundJson;
 import com.ning.billing.jaxrs.json.SubscriptionJsonNoEvents;
 import com.ning.billing.jaxrs.resources.JaxrsResource;
 import com.ning.billing.junction.glue.DefaultJunctionModule;
+import com.ning.billing.overdue.glue.DefaultOverdueModule;
 import com.ning.billing.payment.glue.PaymentModule;
 import com.ning.billing.payment.provider.MockPaymentProviderPluginModule;
 import com.ning.billing.server.ServerTestSuiteWithEmbeddedDB;
@@ -119,7 +121,10 @@ import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableMap;
 import com.google.inject.Module;
 
+import static com.ning.billing.jaxrs.resources.JaxrsResource.ACCOUNTS;
+import static com.ning.billing.jaxrs.resources.JaxrsResource.BUNDLES;
 import static com.ning.billing.jaxrs.resources.JaxrsResource.QUERY_PAYMENT_METHOD_PLUGIN_INFO;
+import static com.ning.billing.jaxrs.resources.JaxrsResource.SUBSCRIPTIONS;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
 
@@ -253,6 +258,7 @@ public class TestJaxrsBase extends ServerTestSuiteWithEmbeddedDB {
             install(new PaymentMockModule());
             install(new BeatrixModule());
             install(new DefaultJunctionModule());
+            install(new DefaultOverdueModule());
             installClock();
         }
 
@@ -941,6 +947,33 @@ public class TestJaxrsBase extends ServerTestSuiteWithEmbeddedDB {
     }
 
     //
+    // OVERDUE
+    //
+
+    protected OverdueStateJson getOverdueStateForAccount(final String accountId) throws Exception {
+        return doGetOverdueState(accountId, ACCOUNTS);
+    }
+
+    protected OverdueStateJson getOverdueStateForBundle(final String bundleId) throws Exception {
+        return doGetOverdueState(bundleId, BUNDLES);
+    }
+
+    protected OverdueStateJson getOverdueStateForSubscription(final String subscriptionId) throws Exception {
+        return doGetOverdueState(subscriptionId, SUBSCRIPTIONS);
+    }
+
+    protected OverdueStateJson doGetOverdueState(final String id, final String resourceType) throws Exception {
+        final String overdueURI = JaxrsResource.OVERDUE_PATH + "/" + resourceType + "/" + id;
+        final Response overdueResponse = doGet(overdueURI, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(overdueResponse.getStatusCode(), Status.OK.getStatusCode());
+
+        final OverdueStateJson overdueStateJson = mapper.readValue(overdueResponse.getResponseBody(), OverdueStateJson.class);
+        assertNotNull(overdueStateJson);
+
+        return overdueStateJson;
+    }
+
+    //
     // HTTP CLIENT HELPERS
     //
     protected Response doPost(final String uri, @Nullable final String body, final Map<String, String> queryParams, final int timeoutSec) {
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestOverdue.java b/server/src/test/java/com/ning/billing/jaxrs/TestOverdue.java
new file mode 100644
index 0000000..2f05d69
--- /dev/null
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestOverdue.java
@@ -0,0 +1,72 @@
+/*
+ * 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.jaxrs;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.jaxrs.json.AccountJson;
+import com.ning.billing.jaxrs.json.InvoiceJsonSimple;
+import com.ning.billing.jaxrs.json.InvoiceJsonWithItems;
+
+import static org.testng.Assert.assertEquals;
+
+public class TestOverdue extends TestJaxrsBase {
+
+    @Test(groups = "slow")
+    public void testOverdueStatus() throws Exception {
+        // Create an account without a payment method
+        final AccountJson accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice();
+
+        // Get the invoices
+        final List<InvoiceJsonWithItems> invoices = getInvoicesWithItemsForAccount(accountJson.getAccountId());
+        // 2 invoices but look for the non zero dollar one
+        assertEquals(invoices.size(), 2);
+        final String bundleId = invoices.get(1).getItems().get(0).getBundleId();
+
+        // We're still clear - see the configuration
+        Assert.assertTrue(getOverdueStateForBundle(bundleId).isClearState());
+
+        clock.addDays(30);
+        crappyWaitForLackOfProperSynchonization();
+        Assert.assertEquals(getOverdueStateForBundle(bundleId).getName(), "OD1");
+
+        clock.addDays(10);
+        crappyWaitForLackOfProperSynchonization();
+        Assert.assertEquals(getOverdueStateForBundle(bundleId).getName(), "OD2");
+
+        clock.addDays(10);
+        crappyWaitForLackOfProperSynchonization();
+        Assert.assertEquals(getOverdueStateForBundle(bundleId).getName(), "OD3");
+
+        // Post external payments
+        for (final InvoiceJsonSimple invoice : getInvoicesForAccount(accountJson.getAccountId())) {
+            if (invoice.getBalance().compareTo(BigDecimal.ZERO) > 0) {
+                createExternalPayment(accountJson, invoice.getInvoiceId(), invoice.getBalance());
+            }
+        }
+
+        // Wait a bit for overdue to pick up the payment events...
+        crappyWaitForLackOfProperSynchonization();
+
+        // Verify we're in clear state
+        Assert.assertTrue(getOverdueStateForBundle(bundleId).isClearState());
+    }
+}
diff --git a/server/src/test/resources/killbill.properties b/server/src/test/resources/killbill.properties
index d00e1dd..1148c07 100644
--- a/server/src/test/resources/killbill.properties
+++ b/server/src/test/resources/killbill.properties
@@ -2,6 +2,7 @@
 com.ning.billing.dbi.jdbc.url=jdbc:mysql://127.0.0.1:3306/killbill
 
 killbill.catalog.uri=catalog-weapons.xml
+killbill.overdue.uri=overdue.xml
 
 killbill.payment.engine.events.off=false
 killbill.payment.retry.days=8,8,8
diff --git a/server/src/test/resources/overdue.xml b/server/src/test/resources/overdue.xml
new file mode 100644
index 0000000..bd13ebe
--- /dev/null
+++ b/server/src/test/resources/overdue.xml
@@ -0,0 +1,43 @@
+<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>5</number>
+           </autoReevaluationInterval>
+       </state>
+   </bundleOverdueStates>
+</overdueConfig>