killbill-memoizeit

See #88 Add new invoice internal API to reschedule a account

10/10/2013 12:27:54 AM

Details

diff --git a/api/src/main/java/com/ning/billing/events/OverdueChangeInternalEvent.java b/api/src/main/java/com/ning/billing/events/OverdueChangeInternalEvent.java
index 20acb43..811e48e 100644
--- a/api/src/main/java/com/ning/billing/events/OverdueChangeInternalEvent.java
+++ b/api/src/main/java/com/ning/billing/events/OverdueChangeInternalEvent.java
@@ -26,4 +26,8 @@ public interface OverdueChangeInternalEvent extends BusInternalEvent {
     String getPreviousOverdueStateName();
 
     String getNextOverdueStateName();
+
+    Boolean isBlockedBilling();
+
+    Boolean isUnblockedBilling();
 }
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceInternalApi.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceInternalApi.java
index a7a80e5..6abfa0c 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceInternalApi.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceInternalApi.java
@@ -22,6 +22,7 @@ import java.util.Map;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
 
 import com.ning.billing.catalog.api.Currency;
@@ -70,4 +71,6 @@ public interface InvoiceInternalApi {
      */
     public void consumeExistingCBAOnAccountWithUnpaidInvoices(final UUID accountId, final InternalCallContext context) throws InvoiceApiException;
 
+
+    public void scheduleInvoiceForAccount(UUID accountId, DateTimeZone accountTimeZone, InternalCallContext context) throws InvoiceApiException;
 }
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java
index 15dd4ea..cba122d 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java
@@ -132,7 +132,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
         // 2012, 6, 31 => P1 (We se 6/31 instead of 6/30 because invoice might happen later in that day)
         addDaysAndCheckForCompletion(7, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
         checkODState("OD1");
-        checkChangePlanWithOverdueState(baseEntitlement, true);
+        checkChangePlanWithOverdueState(baseEntitlement, true, true);
         invoiceChecker.checkInvoice(account.getId(), 3, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
         invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 7, 31), callContext);
 
@@ -149,9 +149,13 @@ public class TestOverdueIntegration extends TestOverdueBase {
         checkODState("OD2");
 
         // 2012, 7, 17 => Retry P1
-        addDaysAndCheckForCompletion(7, NextEvent.PAYMENT_ERROR);
+        addDaysAndCheckForCompletion(7, NextEvent.INVOICE_ADJUSTMENT, NextEvent.PAYMENT_ERROR);
         checkODState("OD2");
 
+        invoiceChecker.checkInvoice(account.getId(), 3,
+                                    callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-166.64")));
+
         // 2012, 7, 18 => Retry P0
         addDaysAndCheckForCompletion(1, NextEvent.PAYMENT_ERROR);
         checkODState("OD2");
@@ -163,29 +167,45 @@ public class TestOverdueIntegration extends TestOverdueBase {
 
         paymentPlugin.makeAllInvoicesFailWithError(false);
         final Collection<Invoice> invoices = invoiceUserApi.getUnpaidInvoicesByAccountId(account.getId(), clock.getUTCToday(), callContext);
+        int remainingUnpaidInvoices = invoices.size();
         for (final Invoice invoice : invoices) {
             if (invoice.getBalance().compareTo(BigDecimal.ZERO) > 0) {
-                createPaymentAndCheckForCompletion(account, invoice, NextEvent.PAYMENT);
+                remainingUnpaidInvoices--;
+                if (remainingUnpaidInvoices > 0) {
+                    createPaymentAndCheckForCompletion(account, invoice, NextEvent.PAYMENT);
+                } else {
+                    createPaymentAndCheckForCompletion(account, invoice, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.PAYMENT);
+                }
             }
         }
-
         checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
-        checkChangePlanWithOverdueState(baseEntitlement, false);
 
-        invoiceChecker.checkRepairedInvoice(account.getId(), 3,
-                                            callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
-                                            // We paid up to 07-31, hence the adjustment
-                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-166.64")),
-                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 23), InvoiceItemType.CBA_ADJ, new BigDecimal("166.64")));
         invoiceChecker.checkInvoice(account.getId(), 4, callContext,
                                     // Item for the upgraded recurring plan
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("64.51")));
+
+        // Do an upgrade now
+        checkChangePlanWithOverdueState(baseEntitlement, false, true);
+
+        invoiceChecker.checkRepairedInvoice(account.getId(), 4, callContext,
+                                            // Item for the upgraded recurring plan
+                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("64.51")),
+                                            // Repair for upgrade
+                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-64.51")),
+                                            // CBA generated
+                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 23), InvoiceItemType.CBA_ADJ, new BigDecimal("64.51")));
+
+
+        invoiceChecker.checkInvoice(account.getId(), 5, callContext,
+                                    // Item for the upgraded recurring plan
                                     new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("154.85")),
-                                    // Credits consumed
-                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 23), InvoiceItemType.CBA_ADJ, new BigDecimal("-154.85")));
+                                    // Repair for upgrade
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 23), InvoiceItemType.CBA_ADJ, new BigDecimal("-64.51")));
+
         invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 7, 31), callContext);
 
-        // Verify the account balance: 249.95 - 74.99 - 154.85
-        assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(new BigDecimal("-11.79")), 0);
+        // Verify the account balance:
+        assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(BigDecimal.ZERO), 0);
     }
 
 
@@ -224,7 +244,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
         // 2012, 6, 31 => P1 (We se 6/31 instead of 6/30 because invoice might happen later in that day)
         addDaysAndCheckForCompletion(7, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
         checkODState("OD1");
-        checkChangePlanWithOverdueState(baseEntitlement, true);
+        checkChangePlanWithOverdueState(baseEntitlement, true, true);
         invoiceChecker.checkInvoice(account.getId(), 3, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
         invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 7, 31), callContext);
 
@@ -237,9 +257,14 @@ public class TestOverdueIntegration extends TestOverdueBase {
         checkODState("OD1");
 
         // 2012, 7, 10 => Retry P0
-        addDaysAndCheckForCompletion(1, NextEvent.PAYMENT_ERROR);
+        addDaysAndCheckForCompletion(1, NextEvent.INVOICE_ADJUSTMENT, NextEvent.PAYMENT_ERROR);
         checkODState("OD2");
 
+        invoiceChecker.checkInvoice(account.getId(), 3,
+                                    callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
+                                    // Repair for the part that was blocked
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-166.64")));
+
         // 2012, 7, 17 => Retry P1
         addDaysAndCheckForCompletion(7, NextEvent.PAYMENT_ERROR);
         checkODState("OD2");
@@ -255,28 +280,31 @@ public class TestOverdueIntegration extends TestOverdueBase {
 
         paymentPlugin.makeAllInvoicesFailWithError(false);
         final Collection<Invoice> invoices = invoiceUserApi.getUnpaidInvoicesByAccountId(account.getId(), clock.getUTCToday(), callContext);
+        int remainingUnpaidInvoices = invoices.size();
         for (final Invoice invoice : invoices) {
             if (invoice.getBalance().compareTo(BigDecimal.ZERO) > 0) {
-                createPaymentAndCheckForCompletion(account, invoice, NextEvent.PAYMENT);
+                remainingUnpaidInvoices--;
+                if (remainingUnpaidInvoices > 0) {
+                    createPaymentAndCheckForCompletion(account, invoice, NextEvent.PAYMENT);
+                } else {
+                    createPaymentAndCheckForCompletion(account, invoice, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.PAYMENT);
+                }
             }
         }
         checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
 
+        invoiceChecker.checkInvoice(account.getId(), 4, callContext,
+                                    // New invoice for the part that was unblocked
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("64.51")));
 
-        // Add 10 days to generate next invoice
-        addDaysAndCheckForCompletion(10, NextEvent.INVOICE, NextEvent.INVOICE_ADJUSTMENT, NextEvent.PAYMENT);
 
-        invoiceChecker.checkRepairedInvoice(account.getId(), 3,
-                                            callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
-                                            // We paid up to 07-31, hence the adjustment
-                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 23), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-102.13")),
-                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 8, 2), new LocalDate(2012, 8, 2), InvoiceItemType.CBA_ADJ, new BigDecimal("102.13")));
+        // Add 10 days to generate next invoice
+        addDaysAndCheckForCompletion(10, NextEvent.INVOICE, NextEvent.PAYMENT);
 
-        invoiceChecker.checkInvoice(account.getId(), 4, callContext,
+        invoiceChecker.checkInvoice(account.getId(), 5, callContext,
                                     // Item for the upgraded recurring plan
-                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 8, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
-                                    // Credits consumed
-                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 8, 2), new LocalDate(2012, 8, 2), InvoiceItemType.CBA_ADJ, new BigDecimal("-102.13")));
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 8, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+
         invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 8, 31), callContext);
 
         // Verify the account balance: 249.95 - 74.99 - 154.85
@@ -321,9 +349,13 @@ public class TestOverdueIntegration extends TestOverdueBase {
         checkODState("OD1");
 
         // 2012, 7, 10 => Retry P0
-        addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR);
+        addDaysAndCheckForCompletion(8,  NextEvent.INVOICE_ADJUSTMENT, NextEvent.PAYMENT_ERROR);
         checkODState("OD2");
 
+        invoiceChecker.checkInvoice(account.getId(), 2,
+                                            callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2013, 5, 31), InvoiceItemType.RECURRING, new BigDecimal("2399.95")),
+                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2013, 5, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-2136.92")));
+
         // 2012, 7, 18 => Retry P0
         addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR);
         checkODState("OD2");
@@ -332,36 +364,49 @@ public class TestOverdueIntegration extends TestOverdueBase {
         addDaysAndCheckForCompletion(5);
         checkODState("OD3");
 
-
         paymentPlugin.makeAllInvoicesFailWithError(false);
         final Collection<Invoice> invoices = invoiceUserApi.getUnpaidInvoicesByAccountId(account.getId(), clock.getUTCToday(), callContext);
+        int remainingUnpaidInvoices = invoices.size();
         for (final Invoice invoice : invoices) {
             if (invoice.getBalance().compareTo(BigDecimal.ZERO) > 0) {
-                createPaymentAndCheckForCompletion(account, invoice, NextEvent.PAYMENT);
+                remainingUnpaidInvoices--;
+                if (remainingUnpaidInvoices > 0) {
+                    createPaymentAndCheckForCompletion(account, invoice, NextEvent.PAYMENT);
+                } else {
+                    createPaymentAndCheckForCompletion(account, invoice, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.PAYMENT);
+                }
             }
         }
         checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
 
+        invoiceChecker.checkInvoice(account.getId(), 3, callContext,
+                                    // New invoice for the part that was unblocked up to the BCD
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("52.56")));
+
+
         // Move to 2012, 7, 31 and Make a change of plan
-        addDaysAndCheckForCompletion(8);
+        addDaysAndCheckForCompletion(8, NextEvent.INVOICE, NextEvent.PAYMENT);
 
-        checkChangePlanWithOverdueState(baseEntitlement, false);
+        invoiceChecker.checkInvoice(account.getId(), 4, callContext,
+                                    // New invoice for the part that was unblocked up to the BCD
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2013, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
 
 
-        invoiceChecker.checkRepairedInvoice(account.getId(), 2,
-                                            callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2013, 5, 31), InvoiceItemType.RECURRING, new BigDecimal("2399.95")),
-                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 23), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-85.4588")),
-                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2013, 5, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-1998.9012")),
-                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 7, 31), InvoiceItemType.CBA_ADJ, new BigDecimal("2084.36")));
 
-        invoiceChecker.checkInvoice(account.getId(), 3, callContext,
+        checkChangePlanWithOverdueState(baseEntitlement, false, false);
+
+        invoiceChecker.checkRepairedInvoice(account.getId(), 4, callContext,
+                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2013, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("2399.95")),
+                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2013, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-2399.95")),
+                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 7, 31), InvoiceItemType.CBA_ADJ, new BigDecimal("2399.95")));
+
+        invoiceChecker.checkInvoice(account.getId(), 5, callContext,
                                     // Item for the upgraded recurring plan
                                     new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 8, 31), InvoiceItemType.RECURRING, new BigDecimal("599.95")),
                                     // Credits consumed
                                     new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 7, 31), InvoiceItemType.CBA_ADJ, new BigDecimal("-599.95")));
         invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 8, 31), callContext);
-
-        assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(new BigDecimal("-1484.41")), 0);
+        assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(new BigDecimal("-1800")), 0);
     }
 
 
@@ -406,57 +451,73 @@ public class TestOverdueIntegration extends TestOverdueBase {
 
         // Now we should be in OD1
         checkODState("OD1");
-        checkChangePlanWithOverdueState(baseEntitlement, true);
+        checkChangePlanWithOverdueState(baseEntitlement, true, true);
 
         // DAY 67 - 37 days after invoice
         addDaysAndCheckForCompletion(2);
 
         // Should still be in OD1
         checkODState("OD1");
-        checkChangePlanWithOverdueState(baseEntitlement, true);
+        checkChangePlanWithOverdueState(baseEntitlement, true, true);
 
         // DAY 75 - 45 days after invoice
-        addDaysAndCheckForCompletion(8);
+        addDaysAndCheckForCompletion(8, NextEvent.INVOICE_ADJUSTMENT);
 
         // Should now be in OD2
         checkODState("OD2");
-        checkChangePlanWithOverdueState(baseEntitlement, true);
+        checkChangePlanWithOverdueState(baseEntitlement, true, true);
+
+
+        invoiceChecker.checkInvoice(account.getId(), 3,
+                                            callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
+                                            // We paid up to 07-31, hence the adjustment
+                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 15), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-124.97")));
 
         // DAY 85 - 55 days after invoice
         addDaysAndCheckForCompletion(10);
 
         // Should now be in OD3
         checkODState("OD3");
-        checkChangePlanWithOverdueState(baseEntitlement, true);
+        checkChangePlanWithOverdueState(baseEntitlement, true, true);
 
         // Add a payment method and set it as default
         paymentApi.addPaymentMethod(BeatrixIntegrationModule.NON_OSGI_PLUGIN_NAME, account, true, paymentMethodPlugin, callContext);
 
         // Pay all invoices
         final Collection<Invoice> invoices = invoiceUserApi.getUnpaidInvoicesByAccountId(account.getId(), clock.getUTCToday(), callContext);
+        int remainingUnpaidInvoices = invoices.size();
         for (final Invoice invoice : invoices) {
             if (invoice.getBalance().compareTo(BigDecimal.ZERO) > 0) {
-                createPaymentAndCheckForCompletion(account, invoice, NextEvent.PAYMENT);
+                remainingUnpaidInvoices--;
+                if (remainingUnpaidInvoices > 0) {
+                    createPaymentAndCheckForCompletion(account, invoice, NextEvent.PAYMENT);
+                } else {
+                    createPaymentAndCheckForCompletion(account, invoice, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.PAYMENT);
+                }
             }
         }
-
         checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
-        checkChangePlanWithOverdueState(baseEntitlement, false);
 
-        invoiceChecker.checkRepairedInvoice(account.getId(), 3,
-                                            callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
-                                            // We paid up to 07-31, hence the adjustment
-                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 15), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-124.97")),
-                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 25), InvoiceItemType.CBA_ADJ, new BigDecimal("124.97")));
         invoiceChecker.checkInvoice(account.getId(), 4, callContext,
                                     // Item for the upgraded recurring plan
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("48.37")));
+
+        invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 7, 31), callContext);
+
+        checkChangePlanWithOverdueState(baseEntitlement, false, true);
+
+        invoiceChecker.checkRepairedInvoice(account.getId(), 4, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("48.37")),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-48.37")),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 25), InvoiceItemType.CBA_ADJ, new BigDecimal("48.37")));
+
+        invoiceChecker.checkInvoice(account.getId(), 5, callContext,
                                     new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("116.09")),
-                                    // Credits consumed
-                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 25), InvoiceItemType.CBA_ADJ, new BigDecimal("-116.09")));
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 25), InvoiceItemType.CBA_ADJ, new BigDecimal("-48.37")));
+
         invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 7, 31), callContext);
 
-        // Verify the account balance: 249.95 - 124.98 - 116.09
-        assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(new BigDecimal("-8.88")), 0);
+        assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(BigDecimal.ZERO), 0);
     }
 
     @Test(groups = "slow")
@@ -543,7 +604,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
         refundPaymentAndCheckForCompletion(account, payment, NextEvent.INVOICE_ADJUSTMENT);
         // We should now be in OD1
         checkODState("OD1");
-        checkChangePlanWithOverdueState(baseEntitlement, true);
+        checkChangePlanWithOverdueState(baseEntitlement, true, true);
     }
 
     @Test(groups = "slow")
@@ -586,7 +647,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
         createChargeBackAndCheckForCompletion(payment, NextEvent.INVOICE_ADJUSTMENT);
         // We should now be in OD1
         checkODState("OD1");
-        checkChangePlanWithOverdueState(baseEntitlement, true);
+        checkChangePlanWithOverdueState(baseEntitlement, true, true);
     }
 
     @Test(groups = "slow")
@@ -624,7 +685,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
 
         // Now we should be in OD1
         checkODState("OD1");
-        checkChangePlanWithOverdueState(baseEntitlement, true);
+        checkChangePlanWithOverdueState(baseEntitlement, true, true);
 
         // We have two unpaid non-zero dollar invoices at this point
         // Pay the first one via an external payment - we should then be 5 days apart from the second invoice
@@ -675,7 +736,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
 
         // Now we should be in OD1
         checkODState("OD1");
-        checkChangePlanWithOverdueState(baseEntitlement, true);
+        checkChangePlanWithOverdueState(baseEntitlement, true, true);
 
         // We have two unpaid non-zero dollar invoices at this point
         // Adjust the first (and only) item of the first invoice - we should then be 5 days apart from the second invoice
@@ -707,7 +768,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
 
         // We should now be in OD1
         checkODState("OD1");
-        checkChangePlanWithOverdueState(baseEntitlement, true);
+        checkChangePlanWithOverdueState(baseEntitlement, true, true);
 
         invoiceChecker.checkInvoice(account.getId(), 4, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 8, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
 
@@ -723,7 +784,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
         checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
     }
 
-    private void checkChangePlanWithOverdueState(final Entitlement entitlement, final boolean shouldFail) {
+    private void checkChangePlanWithOverdueState(final Entitlement entitlement, final boolean shouldFail, final boolean expectedPayment) {
         if (shouldFail) {
             try {
                 entitlement.changePlan("Pistol", term, PriceListSet.DEFAULT_PRICELIST_NAME, callContext);
@@ -732,7 +793,11 @@ public class TestOverdueIntegration extends TestOverdueBase {
             }
         } else {
             // Upgrade - we don't expect a payment here due to the scenario (the account will have some CBA)
-            changeEntitlementAndCheckForCompletion(entitlement, "Assault-Rifle", BillingPeriod.MONTHLY, null, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.INVOICE_ADJUSTMENT);
+            if (expectedPayment) {
+                changeEntitlementAndCheckForCompletion(entitlement, "Assault-Rifle", BillingPeriod.MONTHLY, null, NextEvent.CHANGE, NextEvent.INVOICE_ADJUSTMENT, NextEvent.INVOICE, NextEvent.PAYMENT);
+            } else {
+                changeEntitlementAndCheckForCompletion(entitlement, "Assault-Rifle", BillingPeriod.MONTHLY, null, NextEvent.CHANGE, NextEvent.INVOICE_ADJUSTMENT, NextEvent.INVOICE);
+            }
         }
     }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java b/invoice/src/main/java/com/ning/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java
index e6f6bd1..7f22d92 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java
@@ -18,18 +18,27 @@ package com.ning.billing.invoice.api.svcs;
 
 import java.math.BigDecimal;
 import java.util.Collection;
+import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 
 import javax.inject.Inject;
 
 import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
 
 import com.ning.billing.ErrorCode;
+import com.ning.billing.callcontext.InternalCallContext;
+import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.clock.Clock;
+import com.ning.billing.entitlement.api.Entitlement.EntitlementState;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.api.InvoiceInternalApi;
 import com.ning.billing.invoice.api.InvoicePayment;
 import com.ning.billing.invoice.api.InvoicePaymentType;
 import com.ning.billing.invoice.dao.InvoiceDao;
@@ -37,9 +46,10 @@ import com.ning.billing.invoice.dao.InvoiceModelDao;
 import com.ning.billing.invoice.dao.InvoicePaymentModelDao;
 import com.ning.billing.invoice.model.DefaultInvoice;
 import com.ning.billing.invoice.model.DefaultInvoicePayment;
-import com.ning.billing.callcontext.InternalCallContext;
-import com.ning.billing.callcontext.InternalTenantContext;
-import com.ning.billing.invoice.api.InvoiceInternalApi;
+import com.ning.billing.invoice.notification.NextBillingDatePoster;
+import com.ning.billing.subscription.api.SubscriptionBase;
+import com.ning.billing.subscription.api.SubscriptionBaseInternalApi;
+import com.ning.billing.util.timezone.DateAndTimeZoneContext;
 
 import com.google.common.base.Function;
 import com.google.common.base.Predicate;
@@ -48,10 +58,18 @@ import com.google.common.collect.Collections2;
 public class DefaultInvoiceInternalApi implements InvoiceInternalApi {
 
     private final InvoiceDao dao;
+    private final NextBillingDatePoster nextBillingDatePoster;
+    private final SubscriptionBaseInternalApi subscriptionBaseApi;
+    private final Clock clock;
 
     @Inject
-    public DefaultInvoiceInternalApi(final InvoiceDao dao) {
+    public DefaultInvoiceInternalApi(final InvoiceDao dao, final SubscriptionBaseInternalApi subscriptionBaseApi,
+                                     final Clock clock,
+                                     final NextBillingDatePoster nextBillingDatePoster) {
         this.dao = dao;
+        this.clock = clock;
+        this.subscriptionBaseApi = subscriptionBaseApi;
+        this.nextBillingDatePoster = nextBillingDatePoster;
     }
 
     @Override
@@ -122,4 +140,28 @@ public class DefaultInvoiceInternalApi implements InvoiceInternalApi {
     public void consumeExistingCBAOnAccountWithUnpaidInvoices(final UUID accountId, final InternalCallContext context) throws InvoiceApiException {
         dao.consumeExstingCBAOnAccountWithUnpaidInvoices(accountId, context);
     }
+
+    @Override
+    public void scheduleInvoiceForAccount(final UUID accountId, final DateTimeZone accountTimeZone, final InternalCallContext context) throws InvoiceApiException {
+        final Map<UUID, List<SubscriptionBase>> subscriptions = subscriptionBaseApi.getSubscriptionsForAccount(context);
+        SubscriptionBase targetSubscription = null;
+        for (UUID key : subscriptions.keySet()) {
+            for (SubscriptionBase cur : subscriptions.get(key)) {
+                if (cur.getCategory() == ProductCategory.ADD_ON) {
+                    continue;
+                }
+                if (cur.getState() != EntitlementState.ACTIVE) {
+                    continue;
+                }
+                if (cur.getCurrentPhase() != null && cur.getCurrentPhase().getBillingPeriod() != BillingPeriod.NO_BILLING_PERIOD) {
+                    targetSubscription = cur;
+                    break;
+                }
+            }
+        }
+        if (targetSubscription != null) {
+            final DateAndTimeZoneContext timeZoneContext = new DateAndTimeZoneContext(targetSubscription.getStartDate(), accountTimeZone, clock);
+            nextBillingDatePoster.insertNextBillingNotification(accountId, targetSubscription.getId(), timeZoneContext.computeUTCDateTimeFromNow(), context.getUserToken());
+        }
+    }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
index fe285ab..c0ea740 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
@@ -733,7 +733,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                                              final Map<UUID, DateTime> callbackDateTimePerSubscriptions, final UUID userToken) {
         for (final UUID subscriptionId : callbackDateTimePerSubscriptions.keySet()) {
             final DateTime callbackDateTimeUTC = callbackDateTimePerSubscriptions.get(subscriptionId);
-            nextBillingDatePoster.insertNextBillingNotification(entitySqlDaoWrapperFactory, accountId, subscriptionId, callbackDateTimeUTC, userToken);
+            nextBillingDatePoster.insertNextBillingNotificationFromTransaction(entitySqlDaoWrapperFactory, accountId, subscriptionId, callbackDateTimeUTC, userToken);
         }
     }
 
diff --git a/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDatePoster.java b/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDatePoster.java
index 5ef7697..e3c757e 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDatePoster.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDatePoster.java
@@ -23,12 +23,12 @@ import org.joda.time.DateTime;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.ning.billing.callcontext.InternalCallContext;
 import com.ning.billing.invoice.api.DefaultInvoiceService;
 import com.ning.billing.notificationq.api.NotificationQueue;
 import com.ning.billing.notificationq.api.NotificationQueueService;
 import com.ning.billing.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
 import com.ning.billing.util.callcontext.CallOrigin;
-import com.ning.billing.callcontext.InternalCallContext;
 import com.ning.billing.util.callcontext.InternalCallContextFactory;
 import com.ning.billing.util.callcontext.UserType;
 import com.ning.billing.util.entity.dao.EntitySqlDao;
@@ -51,8 +51,8 @@ public class DefaultNextBillingDatePoster implements NextBillingDatePoster {
     }
 
     @Override
-    public void insertNextBillingNotification(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final UUID accountId,
-                                              final UUID subscriptionId, final DateTime futureNotificationTime, final UUID userToken) {
+    public void insertNextBillingNotificationFromTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final UUID accountId,
+                                                             final UUID subscriptionId, final DateTime futureNotificationTime, final UUID userToken) {
         final InternalCallContext context = createCallContext(accountId, userToken);
 
         final NotificationQueue nextBillingQueue;
@@ -70,6 +70,25 @@ public class DefaultNextBillingDatePoster implements NextBillingDatePoster {
         }
     }
 
+    @Override
+    public void insertNextBillingNotification(final UUID accountId, final UUID subscriptionId, final DateTime futureNotificationTime, final UUID userToken) {
+        final InternalCallContext context = createCallContext(accountId, userToken);
+
+        final NotificationQueue nextBillingQueue;
+        try {
+            nextBillingQueue = notificationQueueService.getNotificationQueue(DefaultInvoiceService.INVOICE_SERVICE_NAME,
+                                                                             DefaultNextBillingDateNotifier.NEXT_BILLING_DATE_NOTIFIER_QUEUE);
+            log.info("Queuing next billing date notification at {} for subscriptionId {}", futureNotificationTime.toString(), subscriptionId.toString());
+
+            nextBillingQueue.recordFutureNotification(futureNotificationTime,
+                                                      new NextBillingDateNotificationKey(subscriptionId), context.getUserToken(), context.getAccountRecordId(), context.getTenantRecordId());
+        } catch (NoSuchNotificationQueue e) {
+            log.error("Attempting to put items on a non-existent queue (NextBillingDateNotifier).", e);
+        } catch (IOException e) {
+            log.error("Failed to serialize notificationKey for subscriptionId {}", subscriptionId);
+        }
+    }
+
     private InternalCallContext createCallContext(final UUID accountId, final UUID userToken) {
         return internalCallContextFactory.createInternalCallContext(accountId, "NextBillingDatePoster", CallOrigin.INTERNAL, UserType.SYSTEM, userToken);
     }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDatePoster.java b/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDatePoster.java
index 65b6d4d..1fe9b11 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDatePoster.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDatePoster.java
@@ -25,6 +25,9 @@ import com.ning.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
 
 public interface NextBillingDatePoster {
 
-    void insertNextBillingNotification(EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, UUID accountId,
+    void insertNextBillingNotificationFromTransaction(EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, UUID accountId,
+                                                      UUID subscriptionId, DateTime futureNotificationTime, UUID userToken);
+
+    void insertNextBillingNotification(UUID accountId,
                                        UUID subscriptionId, DateTime futureNotificationTime, UUID userToken);
 }
diff --git a/invoice/src/test/java/com/ning/billing/invoice/notification/MockNextBillingDatePoster.java b/invoice/src/test/java/com/ning/billing/invoice/notification/MockNextBillingDatePoster.java
index 078b61e..c0bc7aa 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/notification/MockNextBillingDatePoster.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/notification/MockNextBillingDatePoster.java
@@ -26,7 +26,11 @@ import com.ning.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
 public class MockNextBillingDatePoster implements NextBillingDatePoster {
 
     @Override
-    public void insertNextBillingNotification(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final UUID accountId,
-                                              final UUID subscriptionId, final DateTime futureNotificationTime, final UUID userToken) {
+    public void insertNextBillingNotificationFromTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final UUID accountId,
+                                                             final UUID subscriptionId, final DateTime futureNotificationTime, final UUID userToken) {
+    }
+
+    @Override
+    public void insertNextBillingNotification(final UUID accountId, final UUID subscriptionId, final DateTime futureNotificationTime, final UUID userToken) {
     }
 }
diff --git a/overdue/src/main/java/com/ning/billing/overdue/applicator/DefaultOverdueChangeEvent.java b/overdue/src/main/java/com/ning/billing/overdue/applicator/DefaultOverdueChangeEvent.java
index f198239..1c9288c 100644
--- a/overdue/src/main/java/com/ning/billing/overdue/applicator/DefaultOverdueChangeEvent.java
+++ b/overdue/src/main/java/com/ning/billing/overdue/applicator/DefaultOverdueChangeEvent.java
@@ -30,16 +30,23 @@ public class DefaultOverdueChangeEvent extends BusEventBase implements OverdueCh
     private final UUID overdueObjectId;
     private final String previousOverdueStateName;
     private final String nextOverdueStateName;
+    private final Boolean isBlockedBilling;
+    private final Boolean isUnblockedBilling;
+
 
     @JsonCreator
     public DefaultOverdueChangeEvent(@JsonProperty("overdueObjectId") final UUID overdueObjectId,
                                      @JsonProperty("previousOverdueStateName") final String previousOverdueStateName,
                                      @JsonProperty("nextOverdueStateName") final String nextOverdueStateName,
+                                     @JsonProperty("isBlockedBilling") final Boolean isBlockedBilling,
+                                     @JsonProperty("isUnblockedBilling") final Boolean isUnblockedBilling,
                                      @JsonProperty("searchKey1") final Long searchKey1,
                                      @JsonProperty("searchKey2") final Long searchKey2,
                                      @JsonProperty("userToken") final UUID userToken) {
         super(searchKey1, searchKey2, userToken);
         this.overdueObjectId = overdueObjectId;
+        this.isBlockedBilling = isBlockedBilling;
+        this.isUnblockedBilling = isUnblockedBilling;
         this.previousOverdueStateName = previousOverdueStateName;
         this.nextOverdueStateName = nextOverdueStateName;
     }
@@ -65,4 +72,15 @@ public class DefaultOverdueChangeEvent extends BusEventBase implements OverdueCh
         return nextOverdueStateName;
     }
 
+    @Override
+    @JsonProperty("isBlockedBilling")
+    public Boolean isBlockedBilling() {
+        return isBlockedBilling;
+    }
+
+    @Override
+    @JsonProperty("isUnblockedBilling")
+    public Boolean isUnblockedBilling() {
+        return isUnblockedBilling;
+    }
 }
diff --git a/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueStateApplicator.java b/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueStateApplicator.java
index ee412f3..371b839 100644
--- a/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueStateApplicator.java
+++ b/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueStateApplicator.java
@@ -39,6 +39,9 @@ import com.ning.billing.entitlement.api.BlockingStateType;
 import com.ning.billing.entitlement.api.Entitlement;
 import com.ning.billing.entitlement.api.EntitlementApi;
 import com.ning.billing.entitlement.api.EntitlementApiException;
+import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.api.InvoiceInternalApi;
+import com.ning.billing.invoice.api.InvoiceUserApi;
 import com.ning.billing.ovedue.notification.OverdueCheckPoster;
 import com.ning.billing.overdue.OverdueApiException;
 import com.ning.billing.overdue.OverdueCancellationPolicy;
@@ -75,6 +78,7 @@ public class OverdueStateApplicator {
     private final PersistentBus bus;
     private final AccountInternalApi accountApi;
     private final EntitlementApi entitlementApi;
+    private final InvoiceInternalApi invoiceInternalApi;
     private final OverdueEmailGenerator overdueEmailGenerator;
     private final TagInternalApi tagApi;
     private final EmailSender emailSender;
@@ -82,11 +86,13 @@ public class OverdueStateApplicator {
 
     @Inject
     public OverdueStateApplicator(final BlockingInternalApi accessApi, final AccountInternalApi accountApi, final EntitlementApi entitlementApi,
+                                  final InvoiceInternalApi invoiceInternalApi,
                                   final Clock clock, final OverdueCheckPoster poster, final OverdueEmailGenerator overdueEmailGenerator,
                                   final EmailConfig config, final PersistentBus bus, final NonEntityDao nonEntityDao,  final TagInternalApi tagApi) {
         this.blockingApi = accessApi;
         this.accountApi = accountApi;
         this.entitlementApi = entitlementApi;
+        this.invoiceInternalApi = invoiceInternalApi;
         this.clock = clock;
         this.poster = poster;
         this.overdueEmailGenerator = overdueEmailGenerator;
@@ -98,7 +104,7 @@ public class OverdueStateApplicator {
 
 
     public void apply(final OverdueState firstOverdueState, final BillingState billingState,
-                      final Account overdueable, final String previousOverdueStateName,
+                      final Account account, final OverdueState previousOverdueState,
                       final OverdueState nextOverdueState, final InternalCallContext context) throws OverdueException {
         try {
 
@@ -107,7 +113,7 @@ public class OverdueStateApplicator {
                 return;
             }
 
-            log.debug("OverdueStateApplicator:apply <enter> : time = " + clock.getUTCNow() + ", previousState = " + previousOverdueStateName + ", nextState = " + nextOverdueState);
+            log.debug("OverdueStateApplicator:apply <enter> : time = " + clock.getUTCNow() + ", previousState = " + previousOverdueState.getName() + ", nextState = " + nextOverdueState);
 
             final boolean conditionForNextNotfication = !nextOverdueState.isClearState() ||
                                                         // We did not reach the first state yet but we have an unpaid invoice
@@ -115,54 +121,66 @@ public class OverdueStateApplicator {
 
             if (conditionForNextNotfication) {
                 final Period reevaluationInterval = nextOverdueState.isClearState() ? firstOverdueState.getReevaluationInterval() : nextOverdueState.getReevaluationInterval();
-                createFutureNotification(overdueable, clock.getUTCNow().plus(reevaluationInterval), context);
+                createFutureNotification(account, clock.getUTCNow().plus(reevaluationInterval), context);
 
                 log.debug("OverdueStateApplicator <notificationQ> : inserting notification for time = " + clock.getUTCNow().plus(reevaluationInterval));
             } else if (nextOverdueState.isClearState()) {
-                clearFutureNotification(overdueable, context);
+                clearFutureNotification(account, context);
             }
 
-            if (previousOverdueStateName.equals(nextOverdueState.getName())) {
+            if (previousOverdueState.getName().equals(nextOverdueState.getName())) {
                 return;
             }
 
-            storeNewState(overdueable, nextOverdueState, context);
+            storeNewState(account, nextOverdueState, context);
 
-            cancelSubscriptionsIfRequired(overdueable, nextOverdueState, context);
+            cancelSubscriptionsIfRequired(account, nextOverdueState, context);
 
-            sendEmailIfRequired(billingState, overdueable, nextOverdueState, context);
+            triggerInvoiceIfNeeded(account, previousOverdueState, nextOverdueState, context);
+
+            sendEmailIfRequired(billingState, account, nextOverdueState, context);
 
         } catch (OverdueApiException e) {
             if (e.getCode() != ErrorCode.OVERDUE_NO_REEVALUATION_INTERVAL.getCode()) {
                 throw new OverdueException(e);
             }
+        } catch (InvoiceApiException e) {
+            throw new OverdueException(e);
         }
 
-
         try {
-            bus.post(createOverdueEvent(overdueable, previousOverdueStateName, nextOverdueState.getName(), context));
+            bus.post(createOverdueEvent(account, previousOverdueState.getName(), nextOverdueState.getName(),isBlockBillingTransition(previousOverdueState, nextOverdueState),
+                                        isUnblockBillingTransition(previousOverdueState, nextOverdueState), context));
         } catch (Exception e) {
             log.error("Error posting overdue change event to bus", e);
         }
     }
 
-    public void clear(final Account overdueable, final String previousOverdueStateName, final OverdueState clearState, final InternalCallContext context) throws OverdueException {
+    private void triggerInvoiceIfNeeded(final Account account, final OverdueState previousOverdueState, final OverdueState nextOverdueState, final InternalCallContext context) throws InvoiceApiException {
+        if (isBlockBillingTransition(previousOverdueState, nextOverdueState) || isUnblockBillingTransition(previousOverdueState, nextOverdueState)) {
+            invoiceInternalApi.scheduleInvoiceForAccount(account.getId(), account.getTimeZone(), context);
+        }
+    }
+
+    public void clear(final Account overdueable, final OverdueState previousOverdueState, final OverdueState clearState, final InternalCallContext context) throws OverdueException {
 
-        log.debug("OverdueStateApplicator:clear : time = " + clock.getUTCNow() + ", previousState = " + previousOverdueStateName);
+        log.debug("OverdueStateApplicator:clear : time = " + clock.getUTCNow() + ", previousState = " + previousOverdueState.getName());
 
         storeNewState(overdueable, clearState, context);
 
         clearFutureNotification(overdueable, context);
 
         try {
-            bus.post(createOverdueEvent(overdueable, previousOverdueStateName, clearState.getName(), context));
+            bus.post(createOverdueEvent(overdueable, previousOverdueState.getName(), clearState.getName(), isBlockBillingTransition(previousOverdueState, clearState),
+                                        isUnblockBillingTransition(previousOverdueState, clearState), context));
         } catch (Exception e) {
             log.error("Error posting overdue change event to bus", e);
         }
     }
 
-    private OverdueChangeInternalEvent createOverdueEvent(final Account overdueable, final String previousOverdueStateName, final String nextOverdueStateName, final InternalCallContext context) throws BlockingApiException {
-        return new DefaultOverdueChangeEvent(overdueable.getId(), previousOverdueStateName, nextOverdueStateName,
+    private OverdueChangeInternalEvent createOverdueEvent(final Account overdueable, final String previousOverdueStateName, final String nextOverdueStateName,
+                                                          final boolean isBlockedBilling, final boolean isUnblockedBilling, final InternalCallContext context) throws BlockingApiException {
+        return new DefaultOverdueChangeEvent(overdueable.getId(), previousOverdueStateName, nextOverdueStateName, isBlockedBilling, isUnblockedBilling,
                                              context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken());
     }
 
@@ -182,6 +200,14 @@ public class OverdueStateApplicator {
         }
     }
 
+    private boolean isBlockBillingTransition(final OverdueState prevOverdueState, final OverdueState nextOverdueState) {
+        return !blockBilling(prevOverdueState) && blockBilling(nextOverdueState);
+    }
+
+    private boolean isUnblockBillingTransition(final OverdueState prevOverdueState, final OverdueState nextOverdueState) {
+        return blockBilling(prevOverdueState) && !blockBilling(nextOverdueState);
+    }
+
     private boolean blockChanges(final OverdueState nextOverdueState) {
         return nextOverdueState.blockChanges();
     }
diff --git a/overdue/src/main/java/com/ning/billing/overdue/wrapper/OverdueWrapper.java b/overdue/src/main/java/com/ning/billing/overdue/wrapper/OverdueWrapper.java
index cfbaf4c..a8b07da 100644
--- a/overdue/src/main/java/com/ning/billing/overdue/wrapper/OverdueWrapper.java
+++ b/overdue/src/main/java/com/ning/billing/overdue/wrapper/OverdueWrapper.java
@@ -58,16 +58,19 @@ public class OverdueWrapper {
 
         final BillingState billingState = billingState(context);
         final String previousOverdueStateName = api.getBlockingStateForService(overdueable, OverdueService.OVERDUE_SERVICE_NAME, context).getStateName();
+
+        final OverdueState currentOverdueState = overdueStateSet.findState(previousOverdueStateName);
         final OverdueState nextOverdueState = overdueStateSet.calculateOverdueState(billingState, clock.getToday(billingState.getAccountTimeZone()));
 
-        overdueStateApplicator.apply(overdueStateSet.getFirstState(), billingState, overdueable, previousOverdueStateName, nextOverdueState, context);
+        overdueStateApplicator.apply(overdueStateSet.getFirstState(), billingState, overdueable, currentOverdueState, nextOverdueState, context);
 
         return nextOverdueState;
     }
 
     public void clear(final InternalCallContext context) throws OverdueException, OverdueApiException {
         final String previousOverdueStateName = api.getBlockingStateForService(overdueable, OverdueService.OVERDUE_SERVICE_NAME, context).getStateName();
-        overdueStateApplicator.clear(overdueable, previousOverdueStateName, overdueStateSet.getClearState(), context);
+        final OverdueState previousOverdueState = overdueStateSet.findState(previousOverdueStateName);
+        overdueStateApplicator.clear(overdueable, previousOverdueState, overdueStateSet.getClearState(), context);
     }
 
     public BillingState billingState(final InternalTenantContext context) throws OverdueException {
diff --git a/overdue/src/test/java/com/ning/billing/overdue/applicator/TestOverdueStateApplicator.java b/overdue/src/test/java/com/ning/billing/overdue/applicator/TestOverdueStateApplicator.java
index d9ba46c..5f9b236 100644
--- a/overdue/src/test/java/com/ning/billing/overdue/applicator/TestOverdueStateApplicator.java
+++ b/overdue/src/test/java/com/ning/billing/overdue/applicator/TestOverdueStateApplicator.java
@@ -48,20 +48,21 @@ public class TestOverdueStateApplicator extends OverdueTestSuiteWithEmbeddedDB {
         final Account account = Mockito.mock(Account.class);
         Mockito.when(account.getId()).thenReturn(UUID.randomUUID());
 
+        final OverdueState clearState = config.getBundleStateSet().findState(DefaultBlockingState.CLEAR_STATE_NAME);
         OverdueState state;
 
         state = config.getBundleStateSet().findState("OD1");
-        applicator.apply(null, null, account, DefaultBlockingState.CLEAR_STATE_NAME, state, internalCallContext);
+        applicator.apply(null, null, account, clearState, state, internalCallContext);
         testOverdueHelper.checkStateApplied(state);
         checkBussEvent("OD1");
 
         state = config.getBundleStateSet().findState("OD2");
-        applicator.apply(null, null, account, DefaultBlockingState.CLEAR_STATE_NAME, state, internalCallContext);
+        applicator.apply(null, null, account, clearState, state, internalCallContext);
         testOverdueHelper.checkStateApplied(state);
         checkBussEvent("OD2");
 
         state = config.getBundleStateSet().findState("OD3");
-        applicator.apply(null, null, account, DefaultBlockingState.CLEAR_STATE_NAME, state, internalCallContext);
+        applicator.apply(null, null, account, clearState, state, internalCallContext);
         testOverdueHelper.checkStateApplied(state);
         checkBussEvent("OD3");
     }