killbill-uncached

Details

diff --git a/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java b/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java
index 57203fc..829e000 100644
--- a/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java
+++ b/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java
@@ -29,6 +29,7 @@ import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountApiException;
 import org.killbill.billing.account.api.AccountEmail;
 import org.killbill.billing.account.api.AccountInternalApi;
+import org.killbill.billing.account.api.DefaultAccount;
 import org.killbill.billing.account.api.DefaultAccountEmail;
 import org.killbill.billing.account.api.DefaultMutableAccountData;
 import org.killbill.billing.account.api.ImmutableAccountData;
@@ -174,4 +175,15 @@ public class DefaultAccountInternalApi extends DefaultAccountApiBase implements 
         final ObjectType irrelevant = null;
         return new CacheLoaderArgument(irrelevant, args, context);
     }
+
+    @Override
+    public List<Account> getChildrenAccounts(final UUID parentAccountId, final InternalCallContext context) throws AccountApiException {
+        return ImmutableList.<Account>copyOf(Collections2.transform(accountDao.getAccountsByParentId(parentAccountId, context),
+                                                                    new Function<AccountModelDao, Account>() {
+                                                                        @Override
+                                                                        public Account apply(final AccountModelDao input) {
+                                                                            return new DefaultAccount(input);
+                                                                        }
+                                                                    }));
+    }
 }
diff --git a/api/src/main/java/org/killbill/billing/account/api/AccountInternalApi.java b/api/src/main/java/org/killbill/billing/account/api/AccountInternalApi.java
index ad70272..f935ae7 100644
--- a/api/src/main/java/org/killbill/billing/account/api/AccountInternalApi.java
+++ b/api/src/main/java/org/killbill/billing/account/api/AccountInternalApi.java
@@ -43,4 +43,6 @@ public interface AccountInternalApi extends ImmutableAccountInternalApi {
     void updatePaymentMethod(UUID accountId, UUID paymentMethodId, InternalCallContext context) throws AccountApiException;
 
     UUID getByRecordId(Long recordId, InternalTenantContext context) throws AccountApiException;
+
+    List<Account> getChildrenAccounts(UUID parentAccountId, InternalCallContext context) throws AccountApiException;
 }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueBase.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueBase.java
index 29a302c..f7fb6b3 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueBase.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueBase.java
@@ -76,6 +76,10 @@ public abstract class TestOverdueBase extends TestIntegrationBase {
     }
 
     protected void checkODState(final String expected) {
+        checkODState(expected, account.getId());
+    }
+
+    protected void checkODState(final String expected, final UUID accountId) {
         try {
             // This will test the overdue notification queue: when we move the clock, the overdue system
             // should get notified to refresh its state.
@@ -85,13 +89,13 @@ public abstract class TestOverdueBase extends TestIntegrationBase {
             await().atMost(10, SECONDS).until(new Callable<Boolean>() {
                 @Override
                 public Boolean call() throws Exception {
-                    final BlockingState blockingStateForService = blockingApi.getBlockingStateForService(account.getId(), BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, internalCallContext);
+                    final BlockingState blockingStateForService = blockingApi.getBlockingStateForService(accountId, BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, internalCallContext);
                     final String stateName = blockingStateForService != null ? blockingStateForService.getStateName() : OverdueWrapper.CLEAR_STATE_NAME;
                     return expected.equals(stateName);
                 }
             });
         } catch (final Exception e) {
-            final BlockingState blockingStateForService = blockingApi.getBlockingStateForService(account.getId(), BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, internalCallContext);
+            final BlockingState blockingStateForService = blockingApi.getBlockingStateForService(accountId, BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, internalCallContext);
             final String stateName = blockingStateForService != null ? blockingStateForService.getStateName() : OverdueWrapper.CLEAR_STATE_NAME;
             Assert.assertEquals(stateName, expected, "Got exception: " + e.toString());
         }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueChildParentRelationship.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueChildParentRelationship.java
new file mode 100644
index 0000000..fba90a7
--- /dev/null
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueChildParentRelationship.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
+ *
+ * The Billing Project 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 org.killbill.billing.beatrix.integration.overdue;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.api.TestApiListener.NextEvent;
+import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
+import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.entitlement.api.DefaultEntitlement;
+import org.killbill.billing.invoice.api.Invoice;
+import org.killbill.billing.invoice.api.InvoiceItemType;
+import org.killbill.billing.overdue.wrapper.OverdueWrapper;
+import org.testng.annotations.Test;
+import org.weakref.jmx.internal.guava.collect.Iterables;
+
+import static org.testng.Assert.assertEquals;
+
+// For all the tests, we set the the property org.killbill.payment.retry.days=8,8,8,8,8,8,8,8 so that Payment retry logic does not end with an ABORTED state
+// preventing final instant payment to succeed.
+//
+// The tests are difficult to follow because there are actually two tracks of retry in logic:
+// - The payment retries
+// - The overdue notifications
+//
+
+public class TestOverdueChildParentRelationship extends TestOverdueBase {
+
+    @Override
+    public String getOverdueConfig() {
+        final String configXml = "<overdueConfig>" +
+                                 "   <accountOverdueStates>" +
+                                 "       <initialReevaluationInterval>" +
+                                 "           <unit>DAYS</unit><number>10</number>" +
+                                 "       </initialReevaluationInterval>" +
+                                 "       <state name=\"OD3\">" +
+                                 "           <condition>" +
+                                 "               <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                                 "                   <unit>DAYS</unit><number>26</number>" +
+                                 "               </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                                 "           </condition>" +
+                                 "           <externalMessage>Reached OD3</externalMessage>" +
+                                 "           <blockChanges>true</blockChanges>" +
+                                 "           <disableEntitlementAndChangesBlocked>true</disableEntitlementAndChangesBlocked>" +
+                                 "       </state>" +
+                                 "       <state name=\"OD2\">" +
+                                 "           <condition>" +
+                                 "               <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                                 "                   <unit>DAYS</unit><number>18</number>" +
+                                 "               </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                                 "           </condition>" +
+                                 "           <externalMessage>Reached OD2</externalMessage>" +
+                                 "           <blockChanges>true</blockChanges>" +
+                                 "           <disableEntitlementAndChangesBlocked>true</disableEntitlementAndChangesBlocked>" +
+                                 "           <autoReevaluationInterval>" +
+                                 "               <unit>DAYS</unit><number>8</number>" +
+                                 "           </autoReevaluationInterval>" +
+                                 "       </state>" +
+                                 "       <state name=\"OD1\">" +
+                                 "           <condition>" +
+                                 "               <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                                 "                   <unit>DAYS</unit><number>10</number>" +
+                                 "               </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                                 "           </condition>" +
+                                 "           <externalMessage>Reached OD1</externalMessage>" +
+                                 "           <blockChanges>true</blockChanges>" +
+                                 "           <disableEntitlementAndChangesBlocked>false</disableEntitlementAndChangesBlocked>" +
+                                 "           <autoReevaluationInterval>" +
+                                 "               <unit>DAYS</unit><number>8</number>" +
+                                 "           </autoReevaluationInterval>" +
+                                 "       </state>" +
+                                 "   </accountOverdueStates>" +
+                                 "</overdueConfig>";
+
+        return configXml;
+    }
+
+    @Test(groups = "slow", description = "Test overdue stages and return to clear on CTD for Parent and Child accounts")
+    public void testOverdueStagesParentChildAccounts() throws Exception {
+        // 2012-05-01T00:03:42.000Z
+        clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
+
+        setupAccount();
+        final Account childAccount = createAccountWithNonOsgiPaymentMethod(getChildAccountData(0, account.getId(), true));
+
+        // Set next invoice to fail and create subscription
+        paymentPlugin.makeAllInvoicesFailWithError(true);
+        final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(childAccount.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+        bundle = subscriptionApi.getSubscriptionBundle(baseEntitlement.getBundleId(), callContext);
+
+        invoiceChecker.checkInvoice(childAccount.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+        //invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.PARENT_SUMMARY, new BigDecimal("0")));
+        invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 5, 1), callContext);
+
+        // 2012-05-2 => DAY 1 : Parent Invoice commit status
+        addDaysAndCheckForCompletion(1, NextEvent.INVOICE);
+
+        // 2012-05-31 => DAY 30 have to get out of trial {I0, P0}
+        addDaysAndCheckForCompletion(29, NextEvent.PHASE, NextEvent.INVOICE);
+
+        // 2012-06-01 => Parent Invoice payment attempt
+        addDaysAndCheckForCompletion(1, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+
+        invoiceChecker.checkInvoice(childAccount.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+        //invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.PARENT_SUMMARY, new BigDecimal("249.95")));
+        invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
+
+        // 2012-06-09 => DAY 8 : Retry P0
+        addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME, account.getId());
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME, childAccount.getId());
+
+        // 2012-06-11 => Day 10 - Retry P0 - Move to OD1 state
+        addDaysAndCheckForCompletion(2, NextEvent.BLOCK, NextEvent.BLOCK);
+        checkODState("OD1", account.getId());
+        checkODState("OD1", childAccount.getId());
+
+        // 2012-06-17 => DAY 16 - Retry P1
+        addDaysAndCheckForCompletion(6, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+        checkODState("OD1", account.getId());
+        checkODState("OD1", childAccount.getId());
+
+        // 2012-06-19 => Day 18 - Retry P0 - Move to OD2 state
+        addDaysAndCheckForCompletion(2, NextEvent.TAG, NextEvent.BLOCK, NextEvent.TAG, NextEvent.BLOCK);
+        checkODState("OD2", account.getId());
+        checkODState("OD2", childAccount.getId());
+
+        // 2012-06-25 => DAY 24 - Retry P2
+        addDaysAndCheckForCompletion(6, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+        checkODState("OD2", account.getId());
+        checkODState("OD2", childAccount.getId());
+
+        // 2012-06-27 => Day 26 - Retry P2 - Move to OD3 state
+        addDaysAndCheckForCompletion(2, NextEvent.BLOCK, NextEvent.BLOCK);
+        checkODState("OD3", account.getId());
+        checkODState("OD3", childAccount.getId());
+
+        // Make sure the 'invoice-service:next-billing-date-queue' gets processed before we continue and since we are in AUTO_INVOICING_OFF
+        // no event (NULL_INVOICE) will be generated and so we can't synchronize on any event, and we need to add a small amount of sleep
+        Thread.sleep(1000);
+
+        // Verify the account balance is 0
+        assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(new BigDecimal("249.95")), 0);
+        assertEquals(invoiceUserApi.getAccountBalance(childAccount.getId(), callContext).compareTo(new BigDecimal("249.95")), 0);
+
+        allowPaymentsAndResetOverdueToClearByPayingAllUnpaidInvoices(1, 1, childAccount);
+
+        // check invoice generated after clear child account
+        invoiceChecker.checkInvoice(childAccount.getId(), 3, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 19), new LocalDate(2012, 6, 27), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-66.65")),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 27), new LocalDate(2012, 6, 27), InvoiceItemType.CBA_ADJ, new BigDecimal("66.65")));
+
+        // Verify the account balance is now 0
+        assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(BigDecimal.ZERO), 0);
+        assertEquals(invoiceUserApi.getAccountBalance(childAccount.getId(), callContext).compareTo(BigDecimal.valueOf(-66.65)), 0);
+    }
+
+    @Test(groups = "slow", description = "Test overdue stages and return to clear on CTD for Parent and Child accounts")
+    public void testOverdueStagesParentMultiChildAccounts() throws Exception {
+        // 2012-05-01T00:03:42.000Z
+        clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
+
+        setupAccount();
+        final Account childAccount1 = createAccountWithNonOsgiPaymentMethod(getChildAccountData(0, account.getId(), true));
+        final Account childAccount2 = createAccountWithNonOsgiPaymentMethod(getChildAccountData(0, account.getId(), true));
+        final Account childAccount3 = createAccountWithNonOsgiPaymentMethod(getChildAccountData(0, account.getId(), true));
+        final Account childAccountNoPaymentDelegated = createAccountWithNonOsgiPaymentMethod(getChildAccountData(0, account.getId(), false));
+
+        // Set next invoice to fail and create subscription
+        paymentPlugin.makeAllInvoicesFailWithError(true);
+        final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(childAccount1.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+        bundle = subscriptionApi.getSubscriptionBundle(baseEntitlement.getBundleId(), callContext);
+
+        invoiceChecker.checkInvoice(childAccount1.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+        invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 5, 1), callContext);
+
+        // 2012-05-2 => DAY 1 : Parent Invoice commit status
+        addDaysAndCheckForCompletion(1, NextEvent.INVOICE);
+
+        // 2012-05-2 => DAY 2
+        addDaysAndCheckForCompletion(1);
+        final DefaultEntitlement baseEntitlement2 = createBaseEntitlementAndCheckForCompletion(childAccount2.getId(), "externalKey2", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+
+        invoiceChecker.checkInvoice(childAccount2.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 3), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+        invoiceChecker.checkChargedThroughDate(baseEntitlement2.getId(), new LocalDate(2012, 5, 3), callContext);
+
+        // 2012-05-3 => DAY 3 : Parent Invoice commit status  (Sub.2)
+        addDaysAndCheckForCompletion(1, NextEvent.INVOICE);
+
+        // 2012-05-31 => DAY 30 have to get out of trial {I0, P0}
+        addDaysAndCheckForCompletion(27, NextEvent.PHASE, NextEvent.INVOICE);
+
+        // 2012-06-01 => Parent Invoice payment attempt
+        addDaysAndCheckForCompletion(1, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+
+        // 2012-06-02 => DAY 30 have to get out of trial {I0, P0}  (Sub.2)
+        addDaysAndCheckForCompletion(1, NextEvent.PHASE, NextEvent.INVOICE);
+
+        // 2012-06-03 => Parent Invoice payment attempt  (Sub.2)
+        addDaysAndCheckForCompletion(1, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+
+        invoiceChecker.checkInvoice(childAccount1.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+        invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
+
+        // 2012-06-09 => DAY 8 : Retry P0
+        addDaysAndCheckForCompletion(6, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME, account.getId());
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME, childAccount1.getId());
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME, childAccount2.getId());
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME, childAccount3.getId());
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME, childAccountNoPaymentDelegated.getId());
+
+        // 2012-06-11 => Day 10 - Retry P0 - Move to OD1 state
+        addDaysAndCheckForCompletion(2, NextEvent.BLOCK, NextEvent.BLOCK, NextEvent.BLOCK, NextEvent.BLOCK,
+                                     NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+        checkODState("OD1", account.getId());
+        checkODState("OD1", childAccount1.getId());
+        checkODState("OD1", childAccount2.getId());
+        checkODState("OD1", childAccount3.getId());
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME, childAccountNoPaymentDelegated.getId());
+
+        // 2012-06-17 => DAY 16 : Retry P0
+        addDaysAndCheckForCompletion(6, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+        checkODState("OD1", account.getId());
+        checkODState("OD1", childAccount1.getId());
+        checkODState("OD1", childAccount2.getId());
+        checkODState("OD1", childAccount3.getId());
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME, childAccountNoPaymentDelegated.getId());
+
+        // 2012-06-19 => Day 18 - Retry P0 - Move to OD2 state
+        addDaysAndCheckForCompletion(2, NextEvent.TAG, NextEvent.BLOCK, NextEvent.TAG, NextEvent.BLOCK, NextEvent.TAG,
+                                     NextEvent.BLOCK, NextEvent.TAG, NextEvent.BLOCK,
+                                     NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+        checkODState("OD2", account.getId());
+        checkODState("OD2", childAccount1.getId());
+        checkODState("OD2", childAccount2.getId());
+        checkODState("OD2", childAccount3.getId());
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME, childAccountNoPaymentDelegated.getId());
+
+        allowPaymentsAndResetOverdueToClearByPayingAllUnpaidInvoices(0, 4, childAccount1, childAccount2, childAccount3);
+
+    }
+
+    private void allowPaymentsAndResetOverdueToClearByPayingAllUnpaidInvoices(final int expectedInvoicesCount, final int expectedNullInvoicesCount, final Account... childAccounts) {
+
+        // Reset plugin so payments should now succeed
+        paymentPlugin.makeAllInvoicesFailWithError(false);
+
+        // build expected event list
+        List<NextEvent> nextEventList = new ArrayList<NextEvent>();
+        nextEventList.addAll(Collections.nCopies(childAccounts.length + 1, NextEvent.BLOCK));
+        nextEventList.addAll(Collections.nCopies(childAccounts.length + 1, NextEvent.TAG));
+        nextEventList.addAll(Collections.nCopies(expectedInvoicesCount, NextEvent.INVOICE));
+        nextEventList.addAll(Collections.nCopies(expectedNullInvoicesCount, NextEvent.NULL_INVOICE));
+        nextEventList.addAll(Arrays.asList(NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT));
+
+        //
+        // We now pay all unpaid invoices.
+        //
+        // Upon paying the last invoice, the overdue system will clear the state and notify invoice that it should re-generate a new invoice
+        // for the part that was unblocked, which explains why on the last payment we expect an additional invoice (and payment if needed).
+        //
+        final List<Invoice> sortedInvoices = getUnpaidInvoicesOrderFromRecent();
+
+        int remainingUnpaidInvoices = sortedInvoices.size();
+        for (final Invoice invoice : sortedInvoices) {
+            if (invoice.getBalance().compareTo(BigDecimal.ZERO) > 0) {
+                remainingUnpaidInvoices--;
+                if (remainingUnpaidInvoices > 0) {
+                    createPaymentAndCheckForCompletion(account, invoice, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+                } else {
+                    createPaymentAndCheckForCompletion(account, invoice, Iterables.toArray(nextEventList, NextEvent.class));
+                }
+            }
+        }
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
+        for (Account childAccount : childAccounts) {
+            checkODState(OverdueWrapper.CLEAR_STATE_NAME, childAccount.getId());
+        }
+    }
+
+    private List<Invoice> getUnpaidInvoicesOrderFromRecent() {
+        final Collection<Invoice> invoices = invoiceUserApi.getUnpaidInvoicesByAccountId(account.getId(), clock.getUTCToday(), callContext);
+        // Sort in reverse order to first pay most recent invoice-- that way overdue state may only flip when we reach the last one.
+        final List<Invoice> sortedInvoices = new LinkedList<Invoice>(invoices);
+        Collections.sort(sortedInvoices, new Comparator<Invoice>() {
+            @Override
+            public int compare(final Invoice i1, final Invoice i2) {
+                return i2.getInvoiceDate().compareTo(i1.getInvoiceDate());
+            }
+        });
+        return sortedInvoices;
+    }
+
+
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java
index 6d579da..139ac52 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java
@@ -179,7 +179,8 @@ public class InvoiceDaoHelper {
                 final InvoiceModelDao invoice = (in.getParentInvoice() == null) ? in : in.getParentInvoice();
                 final BigDecimal balance = InvoiceModelDaoHelper.getBalance(invoice);
                 log.debug("Computed balance={} for invoice={}", balance, in);
-                return InvoiceStatus.COMMITTED.equals(in.getStatus()) && (balance.compareTo(BigDecimal.ZERO) >= 1) && (upToDate == null || !in.getTargetDate().isAfter(upToDate));
+                return InvoiceStatus.COMMITTED.equals(in.getStatus()) && (balance.compareTo(BigDecimal.ZERO) >= 1) &&
+                       (upToDate == null || in.getTargetDate() == null || !in.getTargetDate().isAfter(upToDate));
             }
         });
         return new ArrayList<InvoiceModelDao>(unpaidInvoices);
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java b/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
index 867b81f..a0abc46 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
@@ -18,11 +18,14 @@
 
 package org.killbill.billing.overdue.listener;
 
+import java.util.List;
 import java.util.UUID;
 
 import javax.inject.Named;
 
 import org.killbill.billing.ObjectType;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.account.api.AccountInternalApi;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.events.ControlTagCreationInternalEvent;
@@ -66,6 +69,7 @@ public class OverdueListener {
     private final OverduePoster asyncPoster;
     private final OverdueConfigCache overdueConfigCache;
     private final NonEntityDao nonEntityDao;
+    private final AccountInternalApi accountApi;
 
     @Inject
     public OverdueListener(final NonEntityDao nonEntityDao,
@@ -73,13 +77,15 @@ public class OverdueListener {
                            final Clock clock,
                            @Named(DefaultOverdueModule.OVERDUE_NOTIFIER_ASYNC_BUS_NAMED)  final OverduePoster asyncPoster,
                            final OverdueConfigCache overdueConfigCache,
-                           final InternalCallContextFactory internalCallContextFactory) {
+                           final InternalCallContextFactory internalCallContextFactory,
+                           final AccountInternalApi accountApi) {
         this.nonEntityDao = nonEntityDao;
         this.clock = clock;
         this.asyncPoster = asyncPoster;
         this.overdueConfigCache = overdueConfigCache;
         this.cacheControllerDispatcher = cacheControllerDispatcher;
         this.internalCallContextFactory = internalCallContextFactory;
+        this.accountApi = accountApi;
     }
 
     @AllowConcurrentEvents
@@ -139,8 +145,26 @@ public class OverdueListener {
         final boolean shouldInsertNotification = shouldInsertNotification(callContext);
 
         if (shouldInsertNotification) {
-            final OverdueAsyncBusNotificationKey notificationKey = new OverdueAsyncBusNotificationKey(accountId, action);
+            OverdueAsyncBusNotificationKey notificationKey = new OverdueAsyncBusNotificationKey(accountId, action);
             asyncPoster.insertOverdueNotification(accountId, clock.getUTCNow(), OverdueAsyncBusNotifier.OVERDUE_ASYNC_BUS_NOTIFIER_QUEUE, notificationKey, callContext);
+
+            try {
+                final List<Account> childrenAccounts = accountApi.getChildrenAccounts(accountId, callContext);
+                if (childrenAccounts != null) {
+                    for (Account childAccount : childrenAccounts) {
+
+                        if (childAccount.isPaymentDelegatedToParent()) {
+                            final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(childAccount.getId(), callContext);
+                            final InternalCallContext accountContext = internalCallContextFactory.createInternalCallContext(internalTenantContext.getAccountRecordId(), callContext);
+                            notificationKey = new OverdueAsyncBusNotificationKey(childAccount.getId(), action);
+                            asyncPoster.insertOverdueNotification(childAccount.getId(), clock.getUTCNow(), OverdueAsyncBusNotifier.OVERDUE_ASYNC_BUS_NOTIFIER_QUEUE, notificationKey, accountContext);
+                        }
+                    }
+                }
+            } catch (Exception e) {
+                log.error("Error loading child accounts from account " + accountId);
+            }
+
         }
     }
 
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapper.java b/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapper.java
index 163786d..0c1e151 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapper.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapper.java
@@ -33,6 +33,7 @@ import org.killbill.billing.overdue.calculator.BillingStateCalculator;
 import org.killbill.billing.overdue.config.api.BillingState;
 import org.killbill.billing.overdue.config.api.OverdueException;
 import org.killbill.billing.overdue.config.api.OverdueStateSet;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.globallocker.LockerType;
 import org.killbill.clock.Clock;
 import org.killbill.commons.locker.GlobalLock;
@@ -41,8 +42,6 @@ import org.killbill.commons.locker.LockFailedException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.MoreObjects;
-
 public class OverdueWrapper {
 
 
@@ -60,6 +59,7 @@ public class OverdueWrapper {
     private final OverdueStateSet overdueStateSet;
     private final BillingStateCalculator billingStateCalcuator;
     private final OverdueStateApplicator overdueStateApplicator;
+    private final InternalCallContextFactory internalCallContextFactory;
 
     public OverdueWrapper(final ImmutableAccountData overdueable,
                           final BlockingInternalApi api,
@@ -67,7 +67,8 @@ public class OverdueWrapper {
                           final GlobalLocker locker,
                           final Clock clock,
                           final BillingStateCalculator billingStateCalcuator,
-                          final OverdueStateApplicator overdueStateApplicator) {
+                          final OverdueStateApplicator overdueStateApplicator,
+                          final InternalCallContextFactory internalCallContextFactory) {
         this.overdueable = overdueable;
         this.overdueStateSet = overdueStateSet;
         this.api = api;
@@ -75,6 +76,7 @@ public class OverdueWrapper {
         this.clock = clock;
         this.billingStateCalcuator = billingStateCalcuator;
         this.overdueStateApplicator = overdueStateApplicator;
+        this.internalCallContextFactory = internalCallContextFactory;
     }
 
     public OverdueState refresh(final DateTime effectiveDate, final InternalCallContext context) throws OverdueException, OverdueApiException {
@@ -131,7 +133,13 @@ public class OverdueWrapper {
         overdueStateApplicator.clear(effectiveDate, overdueable, previousOverdueState, overdueStateSet.getClearState(), context);
     }
 
-    public BillingState billingState(final InternalTenantContext context) throws OverdueException {
+    public BillingState billingState(final InternalCallContext context) throws OverdueException {
+        if ((overdueable.getParentAccountId() != null) && (overdueable.isPaymentDelegatedToParent())) {
+            // calculate billing state from parent account
+            final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(overdueable.getParentAccountId(), context);
+            final InternalCallContext parentAccountContext = internalCallContextFactory.createInternalCallContext(internalTenantContext.getAccountRecordId(), context);
+            return billingStateCalcuator.calculateBillingState(overdueable, parentAccountContext);
+        }
         return billingStateCalcuator.calculateBillingState(overdueable, context);
     }
 }
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapperFactory.java b/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapperFactory.java
index bec4c53..aa43f69 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapperFactory.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapperFactory.java
@@ -36,6 +36,7 @@ import org.killbill.billing.overdue.config.DefaultOverdueState;
 import org.killbill.billing.overdue.config.DefaultOverdueStateSet;
 import org.killbill.billing.overdue.config.api.OverdueException;
 import org.killbill.billing.overdue.config.api.OverdueStateSet;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.clock.Clock;
 import org.killbill.commons.locker.GlobalLocker;
 import org.slf4j.Logger;
@@ -54,6 +55,7 @@ public class OverdueWrapperFactory {
     private final GlobalLocker locker;
     private final Clock clock;
     private final OverdueConfigCache overdueConfigCache;
+    private final InternalCallContextFactory internalCallContextFactory;
 
     @Inject
     public OverdueWrapperFactory(final BlockingInternalApi api,
@@ -62,7 +64,8 @@ public class OverdueWrapperFactory {
                                  final BillingStateCalculator billingStateCalculator,
                                  final OverdueStateApplicator overdueStateApplicatorBundle,
                                  final OverdueConfigCache overdueConfigCache,
-                                 final AccountInternalApi accountApi) {
+                                 final AccountInternalApi accountApi,
+                                 final InternalCallContextFactory internalCallContextFactory) {
         this.billingStateCalculator = billingStateCalculator;
         this.overdueStateApplicator = overdueStateApplicatorBundle;
         this.accountApi = accountApi;
@@ -70,16 +73,17 @@ public class OverdueWrapperFactory {
         this.locker = locker;
         this.clock = clock;
         this.overdueConfigCache = overdueConfigCache;
+        this.internalCallContextFactory = internalCallContextFactory;
     }
 
     public OverdueWrapper createOverdueWrapperFor(final ImmutableAccountData blockable, final InternalTenantContext context) throws OverdueException {
-        return new OverdueWrapper(blockable, api, getOverdueStateSet(context), locker, clock, billingStateCalculator, overdueStateApplicator);
+        return new OverdueWrapper(blockable, api, getOverdueStateSet(context), locker, clock, billingStateCalculator, overdueStateApplicator, internalCallContextFactory);
     }
 
     public OverdueWrapper createOverdueWrapperFor(final UUID id, final InternalTenantContext context) throws OverdueException {
         try {
             final ImmutableAccountData account = accountApi.getImmutableAccountDataById(id, context);
-            return new OverdueWrapper(account, api, getOverdueStateSet(context), locker, clock, billingStateCalculator, overdueStateApplicator);
+            return new OverdueWrapper(account, api, getOverdueStateSet(context), locker, clock, billingStateCalculator, overdueStateApplicator, internalCallContextFactory);
         } catch (final AccountApiException e) {
             throw new OverdueException(e);
         }