killbill-memoizeit

Merge branch 'invoice-optimizations' Signed-off-by:

3/13/2017 11:20:35 AM

Details

diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
index fd03318..fa7cc6c 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
@@ -32,6 +32,7 @@ import org.killbill.billing.account.api.AccountData;
 import org.killbill.billing.api.TestApiListener.NextEvent;
 import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
 import org.killbill.billing.beatrix.util.PaymentChecker.ExpectedPaymentCheck;
+import org.killbill.billing.catalog.api.BillingActionPolicy;
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.catalog.api.PriceListSet;
@@ -609,6 +610,106 @@ public class TestIntegration extends TestIntegrationBase {
         checkNoMoreInvoiceToGenerate(account);
     }
 
+    @Test(groups = "slow", description = "https://github.com/killbill/killbill/issues/715")
+    public void testWithPauseResumeAfterENT_CANCELLEDBlockingState() throws Exception {
+        final DateTime initialDate = new DateTime(2012, 2, 1, 0, 3, 42, 0, testTimeZone);
+        final int billingDay = 2;
+        // set clock to the initial start date
+        clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+        final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(billingDay));
+        final UUID accountId = account.getId();
+        assertNotNull(account);
+
+        final String productName = "Shotgun";
+        final BillingPeriod term = BillingPeriod.MONTHLY;
+
+        //
+        // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE, NextEvent.BLOCK NextEvent.INVOICE
+        //
+        final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+        assertNotNull(baseEntitlement);
+
+        //
+        // VERIFY CTD HAS BEEN SET
+        //
+        final DefaultSubscriptionBase subscription = (DefaultSubscriptionBase) baseEntitlement.getSubscriptionBase();
+        final DateTime startDate = subscription.getCurrentPhaseStart();
+        final BigDecimal rate = subscription.getCurrentPhase().getFixed().getPrice().getPrice(Currency.USD);
+        verifyTestResult(accountId, subscription.getId(), startDate, null, rate, clock.getUTCNow(), 1);
+
+        //
+        // MOVE TIME TO AFTER TRIAL (2012-03-04)
+        //
+        busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+        clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
+        assertListenerStatus();
+
+        invoiceChecker.checkInvoice(accountId,
+                                    1,
+                                    callContext,
+                                    ImmutableList.<ExpectedInvoiceItemCheck>of(new ExpectedInvoiceItemCheck(new LocalDate(2012, 2, 1), null, InvoiceItemType.FIXED, BigDecimal.ZERO)));
+
+        invoiceChecker.checkInvoice(accountId,
+                                    2,
+                                    callContext,
+                                    ImmutableList.<ExpectedInvoiceItemCheck>of(new ExpectedInvoiceItemCheck(new LocalDate(2012, 3, 2), new LocalDate(2012, 4, 2), InvoiceItemType.RECURRING, new BigDecimal("249.95"))));
+
+        // Pause the entitlement between 2012-03-05 and 2012-03-15
+        DefaultEntitlement entitlement = (DefaultEntitlement) entitlementApi.getEntitlementForId(baseEntitlement.getId(), callContext);
+        entitlementApi.pause(entitlement.getBundleId(), new LocalDate(2012, 3, 5), ImmutableList.<PluginProperty>of(), callContext);
+        entitlementApi.resume(entitlement.getBundleId(), new LocalDate(2012, 3, 15), ImmutableList.<PluginProperty>of(), callContext);
+
+        // Advance clock to 2012-03-07
+        busHandler.pushExpectedEvents(NextEvent.BLOCK, NextEvent.INVOICE);
+        clock.addDays(3);
+        assertListenerStatus();
+
+        invoiceChecker.checkInvoice(accountId,
+                                    2,
+                                    callContext,
+                                    ImmutableList.<ExpectedInvoiceItemCheck>of(new ExpectedInvoiceItemCheck(new LocalDate(2012, 3, 2), new LocalDate(2012, 4, 2), InvoiceItemType.RECURRING, new BigDecimal("249.95"))));
+
+        invoiceChecker.checkInvoice(accountId,
+                                    3,
+                                    callContext,
+                                    ImmutableList.<ExpectedInvoiceItemCheck>of(new ExpectedInvoiceItemCheck(new LocalDate(2012, 3, 5), new LocalDate(2012, 4, 2), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-225.76")),
+                                                                               new ExpectedInvoiceItemCheck(new LocalDate(2012, 3, 7), new LocalDate(2012, 3, 7), InvoiceItemType.CBA_ADJ, new BigDecimal("225.76"))));
+
+        // Entitlement should be blocked
+        entitlement = (DefaultEntitlement) entitlementApi.getEntitlementForId(baseEntitlement.getId(), callContext);
+        Assert.assertEquals(entitlement.getState(), EntitlementState.BLOCKED);
+
+        // Advance clock to 2012-03-12, nothing should happen
+        clock.addDays(5);
+        assertListenerStatus();
+
+        // Entitlement is still blocked
+        entitlement = (DefaultEntitlement) entitlementApi.getEntitlementForId(baseEntitlement.getId(), callContext);
+        Assert.assertEquals(entitlement.getState(), EntitlementState.BLOCKED);
+
+        List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(accountId, false, callContext);
+        assertEquals(invoices.size(), 3);
+
+        // Cancel entitlement start of term but with billing policy immediate (ENT_BLOCKED must be after ENT_CANCELLED to trigger the bug)
+        busHandler.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CANCEL, NextEvent.NULL_INVOICE);
+        baseEntitlement.cancelEntitlementWithDateOverrideBillingPolicy(new LocalDate(2012, 3, 2), BillingActionPolicy.IMMEDIATE, ImmutableList.<PluginProperty>of(), callContext);
+        assertListenerStatus();
+
+        // 2012-03-16
+        busHandler.pushExpectedEvents(NextEvent.BLOCK, NextEvent.NULL_INVOICE);
+        clock.addDays(4);
+        assertListenerStatus();
+
+        busHandler.pushExpectedEvents(NextEvent.NULL_INVOICE);
+        clock.addMonths(3);
+        assertListenerStatus();
+
+        // No new invoices
+        invoices = invoiceUserApi.getInvoicesByAccount(accountId, false, callContext);
+        assertEquals(invoices.size(), 3);
+    }
+
     @Test(groups = "slow")
     public void testForMultipleRecurringPhases() throws Exception {
         final DateTime initialCreationDate = new DateTime(2012, 2, 1, 0, 3, 42, 0, testTimeZone);
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/CBADao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/CBADao.java
index 230fdd1..d2ce6d3 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/CBADao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/CBADao.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
  *
@@ -24,13 +26,12 @@ import java.util.UUID;
 import javax.annotation.Nullable;
 import javax.inject.Inject;
 
-import org.killbill.billing.invoice.api.InvoiceApiException;
-import org.killbill.billing.invoice.api.InvoiceItem;
-import org.killbill.billing.invoice.api.InvoiceItemType;
-import org.killbill.billing.invoice.model.CreditBalanceAdjInvoiceItem;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.entity.EntityPersistenceException;
+import org.killbill.billing.invoice.api.InvoiceApiException;
+import org.killbill.billing.invoice.api.InvoiceStatus;
+import org.killbill.billing.invoice.model.CreditBalanceAdjInvoiceItem;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
 
 import com.google.common.base.Predicate;
@@ -46,41 +47,35 @@ public class CBADao {
         this.invoiceDaoHelper = invoiceDaoHelper;
     }
 
-
-    public BigDecimal getAccountCBAFromTransaction(final UUID accountId,
-                                                    final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory,
-                                                    final InternalTenantContext context) {
-        final List<InvoiceModelDao> invoices = invoiceDaoHelper.getAllInvoicesByAccountFromTransaction(entitySqlDaoWrapperFactory, context);
-        return getAccountCBAFromTransaction(invoices);
-    }
-
-    public BigDecimal getAccountCBAFromTransaction(final List<InvoiceModelDao> invoices) {
-        BigDecimal cba = BigDecimal.ZERO;
-        for (final InvoiceModelDao cur : invoices) {
-            cba = cba.add(InvoiceModelDaoHelper.getCBAAmount(cur));
-        }
-        return cba;
+    // PERF: Compute the CBA directly in the database (faster than re-constructing all invoices)
+    public BigDecimal getAccountCBAFromTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InternalTenantContext context) {
+        final InvoiceItemSqlDao invoiceItemSqlDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
+        return invoiceItemSqlDao.getAccountCBA(context);
     }
 
     // We expect a clean up to date invoice, with all the items except the cba, that we will compute in that method
-    public InvoiceItemModelDao computeCBAComplexity(final InvoiceModelDao invoice, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InternalCallContext context) throws EntityPersistenceException, InvoiceApiException {
-
+    public InvoiceItemModelDao computeCBAComplexity(final InvoiceModelDao invoice,
+                                                    @Nullable final BigDecimal accountCBAOrNull,
+                                                    @Nullable final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory,
+                                                    final InternalCallContext context) throws EntityPersistenceException, InvoiceApiException {
         final BigDecimal balance = getInvoiceBalance(invoice);
 
-        // Current balance is negative, we need to generate a credit (positive CBA amount).
         if (balance.compareTo(BigDecimal.ZERO) < 0) {
-            return new InvoiceItemModelDao(new CreditBalanceAdjInvoiceItem(invoice.getId(), invoice.getAccountId(), context.getCreatedDate().toLocalDate(), balance.negate(), invoice.getCurrency()));
-
-        // Current balance is positive, we need to use some of the existing if available (negative CBA amount)
-        } else if (balance.compareTo(BigDecimal.ZERO) > 0) {
+            // Current balance is negative, we need to generate a credit (positive CBA amount)
+            return buildCBAItem(invoice, balance, context);
+        } else if (balance.compareTo(BigDecimal.ZERO) > 0 && invoice.getStatus() == InvoiceStatus.COMMITTED) {
+            // Current balance is positive and the invoice is COMMITTED, we need to use some of the existing if available (negative CBA amount)
+            // PERF: in some codepaths, the CBA maybe have already been computed
+            BigDecimal accountCBA = accountCBAOrNull;
+            if (accountCBAOrNull == null) {
+                accountCBA = getAccountCBAFromTransaction(entitySqlDaoWrapperFactory, context);
+            }
 
-            final List<InvoiceModelDao> allInvoices = invoiceDaoHelper.getAllInvoicesByAccountFromTransaction(entitySqlDaoWrapperFactory, context);
-            final BigDecimal accountCBA = getAccountCBAFromTransaction(allInvoices);
             if (accountCBA.compareTo(BigDecimal.ZERO) <= 0) {
                 return null;
             }
             final BigDecimal positiveCreditAmount = accountCBA.compareTo(balance) > 0 ? balance : accountCBA;
-            return new InvoiceItemModelDao(new CreditBalanceAdjInvoiceItem(invoice.getId(), invoice.getAccountId(), context.getCreatedDate().toLocalDate(), positiveCreditAmount.negate(), invoice.getCurrency()));
+            return buildCBAItem(invoice, positiveCreditAmount, context);
         } else {
             // 0 balance, nothing to do.
             return null;
@@ -113,64 +108,49 @@ public class CBADao {
     }
 
     // We let the code below rehydrate the invoice before we can add the CBA item
-    public void addCBAComplexityFromTransaction(final UUID invoiceId, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InternalCallContext context) throws EntityPersistenceException, InvoiceApiException {
-
+    // PERF: when possible, prefer the method below to avoid re-fetching the invoice
+    public void doCBAComplexityFromTransaction(final UUID invoiceId,
+                                               final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory,
+                                               final InternalCallContext context) throws EntityPersistenceException, InvoiceApiException {
         final InvoiceSqlDao transInvoiceDao = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class);
         final InvoiceModelDao invoice = transInvoiceDao.getById(invoiceId.toString(), context);
         invoiceDaoHelper.populateChildren(invoice, entitySqlDaoWrapperFactory, context);
-        addCBAComplexityFromTransaction(invoice, entitySqlDaoWrapperFactory, context);
+
+        doCBAComplexityFromTransaction(invoice, entitySqlDaoWrapperFactory, context);
     }
 
-    // We expect a clean up to date invoice, with all the items except the CBA, that we will compute in that method
-    public void addCBAComplexityFromTransaction(final InvoiceModelDao invoice, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InternalCallContext context) throws EntityPersistenceException, InvoiceApiException {
-        final InvoiceItemModelDao cbaItem = computeCBAComplexity(invoice, entitySqlDaoWrapperFactory, context);
-        if (cbaItem != null) {
-            final InvoiceItemSqlDao transInvoiceItemDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
-            transInvoiceItemDao.create(cbaItem, context);
-        }
-        List<InvoiceModelDao> invoiceItemModelDaos = invoiceDaoHelper.getAllInvoicesByAccountFromTransaction(entitySqlDaoWrapperFactory, context);
-        useExistingCBAFromTransaction(invoiceItemModelDaos, entitySqlDaoWrapperFactory, context);
+    public void doCBAComplexityFromTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory,
+                                               final InternalCallContext context) throws EntityPersistenceException, InvoiceApiException {
+        doCBAComplexityFromTransaction((InvoiceModelDao) null, entitySqlDaoWrapperFactory, context);
     }
 
-    public void addCBAComplexityFromTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InternalCallContext context) throws EntityPersistenceException, InvoiceApiException {
+    // Note! We expect an *up-to-date* invoice, with all the items and payments except the CBA, that we will compute in that method
+    public void doCBAComplexityFromTransaction(@Nullable final InvoiceModelDao invoice,
+                                               final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory,
+                                               final InternalCallContext context) throws EntityPersistenceException, InvoiceApiException {
+        // PERF: It is expensive to retrieve and construct all invoice objects. To check if there is effectively something to use, compute the CBA by the database first
+        BigDecimal remainingAccountCBA = getAccountCBAFromTransaction(entitySqlDaoWrapperFactory, context);
 
-        List<InvoiceModelDao> invoiceItemModelDaos = invoiceDaoHelper.getAllInvoicesByAccountFromTransaction(entitySqlDaoWrapperFactory, context);
-        for (InvoiceModelDao cur : invoiceItemModelDaos) {
-            addCBAIfNeeded(entitySqlDaoWrapperFactory, cur, context);
+        if (invoice != null) {
+            // Generate or use CBA for that specific invoice
+            remainingAccountCBA = computeCBAComplexityAndCreateCBAItem(remainingAccountCBA, invoice, entitySqlDaoWrapperFactory, context);
         }
-        invoiceItemModelDaos = invoiceDaoHelper.getAllInvoicesByAccountFromTransaction(entitySqlDaoWrapperFactory, context);
-        useExistingCBAFromTransaction(invoiceItemModelDaos, entitySqlDaoWrapperFactory, context);
-    }
 
-    /**
-     * Adjust the invoice with a CBA item if the new invoice balance is negative.
-     *
-     * @param entitySqlDaoWrapperFactory the EntitySqlDaoWrapperFactory from the current transaction
-     * @param invoice                    the invoice to adjust
-     * @param context                    the call callcontext
-     */
-    private void addCBAIfNeeded(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory,
-                                final InvoiceModelDao invoice,
-                                final InternalCallContext context) throws EntityPersistenceException {
-
-        // If invoice balance becomes negative we add some CBA item
-        final BigDecimal balance = InvoiceModelDaoHelper.getBalance(invoice);
-        if (balance.compareTo(BigDecimal.ZERO) < 0) {
-            final InvoiceItemSqlDao transInvoiceItemDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
-            final InvoiceItemModelDao cbaAdjItem = new InvoiceItemModelDao(new CreditBalanceAdjInvoiceItem(invoice.getId(), invoice.getAccountId(), context.getCreatedDate().toLocalDate(), balance.negate(), invoice.getCurrency()));
-            transInvoiceItemDao.create(cbaAdjItem, context);
-        }
+        useExistingCBAFromTransaction(remainingAccountCBA, entitySqlDaoWrapperFactory, context);
     }
 
-
-    private void useExistingCBAFromTransaction(final List<InvoiceModelDao> invoices, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InternalCallContext context) throws InvoiceApiException, EntityPersistenceException {
-
-        final BigDecimal accountCBA = getAccountCBAFromTransaction(invoices);
+    // Distribute account CBA across all COMMITTED unpaid invoices
+    private void useExistingCBAFromTransaction(final BigDecimal accountCBA,
+                                               final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory,
+                                               final InternalCallContext context) throws InvoiceApiException, EntityPersistenceException {
         if (accountCBA.compareTo(BigDecimal.ZERO) <= 0) {
             return;
         }
 
-        final List<InvoiceModelDao> unpaidInvoices = invoiceDaoHelper.getUnpaidInvoicesByAccountFromTransaction(invoices, null);
+        // PERF: Computing the invoice balance is difficult to do in the DB, so we effectively need to retrieve all invoices on the account and filter the unpaid ones in memory.
+        // This should be infrequent though because of the account CBA check above.
+        final List<InvoiceModelDao> allInvoices = invoiceDaoHelper.getAllInvoicesByAccountFromTransaction(entitySqlDaoWrapperFactory, context);
+        final List<InvoiceModelDao> unpaidInvoices = invoiceDaoHelper.getUnpaidInvoicesByAccountFromTransaction(allInvoices, null);
         // We order the same os BillingStateCalculator-- should really share the comparator
         final List<InvoiceModelDao> orderedUnpaidInvoices = Ordering.from(new Comparator<InvoiceModelDao>() {
             @Override
@@ -180,20 +160,46 @@ public class CBADao {
         }).immutableSortedCopy(unpaidInvoices);
 
         BigDecimal remainingAccountCBA = accountCBA;
-        for (InvoiceModelDao cur : orderedUnpaidInvoices) {
-            final BigDecimal curInvoiceBalance = InvoiceModelDaoHelper.getBalance(cur);
-            final BigDecimal cbaToApplyOnInvoice = remainingAccountCBA.compareTo(curInvoiceBalance) <= 0 ? remainingAccountCBA : curInvoiceBalance;
-            remainingAccountCBA = remainingAccountCBA.subtract(cbaToApplyOnInvoice);
-
-            final InvoiceItemModelDao cbaAdjItem = new InvoiceItemModelDao(new CreditBalanceAdjInvoiceItem(cur.getId(), cur.getAccountId(), context.getCreatedDate().toLocalDate(), cbaToApplyOnInvoice.negate(), cur.getCurrency()));
-
-            final InvoiceItemSqlDao transInvoiceItemDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
-            transInvoiceItemDao.create(cbaAdjItem, context);
-
+        for (final InvoiceModelDao unpaidInvoice : orderedUnpaidInvoices) {
+            remainingAccountCBA = computeCBAComplexityAndCreateCBAItem(remainingAccountCBA, unpaidInvoice, entitySqlDaoWrapperFactory, context);
             if (remainingAccountCBA.compareTo(BigDecimal.ZERO) <= 0) {
                 break;
             }
         }
     }
 
+    // Return the updated account CBA
+    private BigDecimal computeCBAComplexityAndCreateCBAItem(final BigDecimal accountCBA,
+                                                            final InvoiceModelDao invoice,
+                                                            final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory,
+                                                            final InternalCallContext context) throws EntityPersistenceException, InvoiceApiException {
+        final InvoiceItemModelDao cbaItem = computeCBAComplexity(invoice, accountCBA, entitySqlDaoWrapperFactory, context);
+        if (cbaItem != null) {
+            createCBAItem(invoice, cbaItem, entitySqlDaoWrapperFactory, context);
+            return accountCBA.add(cbaItem.getAmount());
+        } else {
+            return accountCBA;
+        }
+    }
+
+    private void createCBAItem(final InvoiceModelDao invoiceModelDao,
+                               final InvoiceItemModelDao cbaItem,
+                               final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory,
+                               final InternalCallContext context) throws EntityPersistenceException {
+        final InvoiceItemSqlDao transInvoiceItemDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
+        transInvoiceItemDao.create(cbaItem, context);
+
+        // Refresh the in-memory item
+        invoiceModelDao.addInvoiceItem(cbaItem);
+    }
+
+    private InvoiceItemModelDao buildCBAItem(final InvoiceModelDao invoice,
+                                             final BigDecimal amount,
+                                             final InternalCallContext context) {
+        return new InvoiceItemModelDao(new CreditBalanceAdjInvoiceItem(invoice.getId(),
+                                                                       invoice.getAccountId(),
+                                                                       context.getCreatedDate().toLocalDate(),
+                                                                       amount.negate(),
+                                                                       invoice.getCurrency()));
+    }
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
index e20bef5..1de777b 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
@@ -20,6 +20,7 @@ package org.killbill.billing.invoice.dao;
 
 import java.math.BigDecimal;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
@@ -290,6 +291,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
         }
         final UUID accountId = invoices.iterator().next().getAccountId();
 
+        final Map<UUID, InvoiceModelDao> invoiceByInvoiceId = new HashMap<UUID, InvoiceModelDao>();
         return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<InvoiceItemModelDao>>() {
             @Override
             public List<InvoiceItemModelDao> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
@@ -298,6 +300,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
 
                 final List<InvoiceItemModelDao> createdInvoiceItems = new LinkedList<InvoiceItemModelDao>();
                 for (final InvoiceModelDao invoiceModelDao : invoices) {
+                    invoiceByInvoiceId.put(invoiceModelDao.getId(), invoiceModelDao);
                     final boolean isRealInvoice = uniqueInvoiceIds.remove(invoiceModelDao.getId());
 
                     // Create the invoice if needed
@@ -336,9 +339,17 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                 }
 
                 for (final UUID adjustedInvoiceId : adjustedInvoiceIds) {
-                    cbaDao.addCBAComplexityFromTransaction(adjustedInvoiceId, entitySqlDaoWrapperFactory, context);
+                    final boolean newInvoice = createdInvoiceIds.contains(adjustedInvoiceId);
+                    if (newInvoice) {
+                        // New invoice, so no associated payment yet: no need to refresh the invoice state
+                        cbaDao.doCBAComplexityFromTransaction(invoiceByInvoiceId.get(adjustedInvoiceId), entitySqlDaoWrapperFactory, context);
+                    } else {
+                        // Existing invoice (e.g. we're processing an adjustment): refresh the invoice state to get the correct balance
+                        // Should we maybe enforce callers (e.g. InvoiceApiHelper) to properly populate these invoices?
+                        cbaDao.doCBAComplexityFromTransaction(adjustedInvoiceId, entitySqlDaoWrapperFactory, context);
+                    }
 
-                    if (committedInvoiceIds.contains(adjustedInvoiceId) && !createdInvoiceIds.contains(adjustedInvoiceId)) {
+                    if (committedInvoiceIds.contains(adjustedInvoiceId) && !newInvoice) {
                         // Notify the bus since the balance of the invoice changed (only if the invoice is COMMITTED)
                         notifyBusOfInvoiceAdjustment(entitySqlDaoWrapperFactory, adjustedInvoiceId, accountId, context.getUserToken(), context);
                     }
@@ -420,7 +431,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
         return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<BigDecimal>() {
             @Override
             public BigDecimal inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
-                return cbaDao.getAccountCBAFromTransaction(accountId, entitySqlDaoWrapperFactory, context);
+                return cbaDao.getAccountCBAFromTransaction(entitySqlDaoWrapperFactory, context);
             }
         });
     }
@@ -539,7 +550,8 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                     }
                 }
 
-                cbaDao.addCBAComplexityFromTransaction(invoice, entitySqlDaoWrapperFactory, context);
+                // The invoice object has been kept up-to-date
+                cbaDao.doCBAComplexityFromTransaction(invoice, entitySqlDaoWrapperFactory, context);
 
                 if (isInvoiceAdjusted) {
                     notifyBusOfInvoiceAdjustment(entitySqlDaoWrapperFactory, invoice.getId(), invoice.getAccountId(), context.getUserToken(), context);
@@ -595,7 +607,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                 // Notify the bus since the balance of the invoice changed
                 final UUID accountId = transactional.getAccountIdFromInvoicePaymentId(chargeBack.getId().toString(), context);
 
-                cbaDao.addCBAComplexityFromTransaction(payment.getInvoiceId(), entitySqlDaoWrapperFactory, context);
+                cbaDao.doCBAComplexityFromTransaction(payment.getInvoiceId(), entitySqlDaoWrapperFactory, context);
 
                 notifyBusOfInvoicePayment(entitySqlDaoWrapperFactory, chargeBack, accountId, context.getUserToken(), context);
 
@@ -631,7 +643,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                 // Notify the bus since the balance of the invoice changed
                 final UUID accountId = transactional.getAccountIdFromInvoicePaymentId(chargebackReversed.getId().toString(), context);
 
-                cbaDao.addCBAComplexityFromTransaction(chargebackReversed.getInvoiceId(), entitySqlDaoWrapperFactory, context);
+                cbaDao.doCBAComplexityFromTransaction(chargebackReversed.getInvoiceId(), entitySqlDaoWrapperFactory, context);
 
                 notifyBusOfInvoicePayment(entitySqlDaoWrapperFactory, chargebackReversed, accountId, context.getUserToken(), context);
 
@@ -645,7 +657,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
         return transactionalSqlDao.execute(InvoiceApiException.class, new EntitySqlDaoTransactionWrapper<InvoiceItemModelDao>() {
             @Override
             public InvoiceItemModelDao inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
-                final InvoiceItemModelDao cbaNewItem = cbaDao.computeCBAComplexity(invoice, entitySqlDaoWrapperFactory, context);
+                final InvoiceItemModelDao cbaNewItem = cbaDao.computeCBAComplexity(invoice, null, entitySqlDaoWrapperFactory, context);
                 return cbaNewItem;
             }
         });
@@ -845,7 +857,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
 
                 // If there is more account credit than CBA we adjusted, we're done.
                 // Otherwise, we need to find further invoices on which this credit was consumed
-                final BigDecimal accountCBA = cbaDao.getAccountCBAFromTransaction(accountId, entitySqlDaoWrapperFactory, context);
+                final BigDecimal accountCBA = cbaDao.getAccountCBAFromTransaction(entitySqlDaoWrapperFactory, context);
                 if (accountCBA.compareTo(BigDecimal.ZERO) < 0) {
                     if (accountCBA.compareTo(cbaItem.getAmount().negate()) < 0) {
                         throw new IllegalStateException("The account balance can't be lower than the amount adjusted");
@@ -904,12 +916,12 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
         });
     }
 
+    @Override
     public void consumeExstingCBAOnAccountWithUnpaidInvoices(final UUID accountId, final InternalCallContext context) {
         transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
             @Override
             public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
-                // In theory we should only have to call useExistingCBAFromTransaction but just to be safe we also check for credit generation
-                cbaDao.addCBAComplexityFromTransaction(entitySqlDaoWrapperFactory, context);
+                cbaDao.doCBAComplexityFromTransaction(entitySqlDaoWrapperFactory, context);
                 return null;
             }
         });
@@ -1026,6 +1038,8 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
 
                 transactional.updateStatus(invoiceId.toString(), newStatus.toString(), context);
 
+                cbaDao.doCBAComplexityFromTransaction(entitySqlDaoWrapperFactory, context);
+
                 if (InvoiceStatus.COMMITTED.equals(newStatus)) {
                     // notify invoice creation event
                     notifyBusOfInvoiceCreation(entitySqlDaoWrapperFactory, invoice, context);
@@ -1173,19 +1187,25 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
 
 
                 // save invoices and invoice items
-                InvoiceModelDao childInvoice = new InvoiceModelDao(invoiceForExternalCharge);
+                final InvoiceModelDao childInvoice = new InvoiceModelDao(invoiceForExternalCharge);
                 invoiceSqlDao.create(childInvoice, childAccountContext);
-                createInvoiceItemFromTransaction(transInvoiceItemSqlDao, new InvoiceItemModelDao(externalChargeItem), childAccountContext);
+                final InvoiceItemModelDao childExternalChargeItem = new InvoiceItemModelDao(externalChargeItem);
+                createInvoiceItemFromTransaction(transInvoiceItemSqlDao, childExternalChargeItem, childAccountContext);
+                // Keep invoice up-to-date for CBA below
+                childInvoice.addInvoiceItem(childExternalChargeItem);
 
-                InvoiceModelDao parentInvoice = new InvoiceModelDao(invoiceForCredit);
+                final InvoiceModelDao parentInvoice = new InvoiceModelDao(invoiceForCredit);
                 invoiceSqlDao.create(parentInvoice, parentAccountContext);
-                createInvoiceItemFromTransaction(transInvoiceItemSqlDao, new InvoiceItemModelDao(creditItem), parentAccountContext);
+                final InvoiceItemModelDao parentCreditItem = new InvoiceItemModelDao(creditItem);
+                createInvoiceItemFromTransaction(transInvoiceItemSqlDao, parentCreditItem, parentAccountContext);
+                // Keep invoice up-to-date for CBA below
+                parentInvoice.addInvoiceItem(parentCreditItem);
 
                 // add CBA complexity and notify bus on child invoice creation
-                cbaDao.addCBAComplexityFromTransaction(childInvoice.getId(), entitySqlDaoWrapperFactory, childAccountContext);
+                cbaDao.doCBAComplexityFromTransaction(childInvoice, entitySqlDaoWrapperFactory, childAccountContext);
                 notifyBusOfInvoiceCreation(entitySqlDaoWrapperFactory, childInvoice, childAccountContext);
 
-                cbaDao.addCBAComplexityFromTransaction(parentInvoice.getId(), entitySqlDaoWrapperFactory, parentAccountContext);
+                cbaDao.doCBAComplexityFromTransaction(parentInvoice, entitySqlDaoWrapperFactory, parentAccountContext);
                 notifyBusOfInvoiceCreation(entitySqlDaoWrapperFactory, parentInvoice, parentAccountContext);
 
                 return null;
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceItemModelDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceItemModelDao.java
index 8805d52..e814a69 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceItemModelDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceItemModelDao.java
@@ -183,6 +183,10 @@ public class InvoiceItemModelDao extends EntityModelDaoBase implements EntityMod
         this.subscriptionId = subscriptionId;
     }
 
+    public void setDescription(final String description) {
+        this.description = description;
+    }
+
     public void setPlanName(final String planName) {
         this.planName = planName;
     }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.java
index e30b5d2..8c7ded7 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
  *
@@ -20,16 +22,15 @@ import java.math.BigDecimal;
 import java.util.List;
 
 import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.util.audit.ChangeType;
 import org.killbill.billing.util.entity.dao.Audited;
+import org.killbill.billing.util.entity.dao.EntitySqlDao;
+import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
 import org.skife.jdbi.v2.sqlobject.Bind;
 import org.skife.jdbi.v2.sqlobject.BindBean;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
-
-import org.killbill.billing.invoice.api.InvoiceItem;
-import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.billing.util.entity.dao.EntitySqlDao;
-import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
 
 @EntitySqlDaoStringTemplate
@@ -57,4 +58,7 @@ public interface InvoiceItemSqlDao extends EntitySqlDao<InvoiceItemModelDao, Inv
     @SqlQuery
     List<InvoiceItemModelDao> getInvoiceItemsByParentInvoice(@Bind("parentInvoiceId") final String parentInvoiceId,
                                                              @BindBean final InternalTenantContext context);
+
+    @SqlQuery
+    BigDecimal getAccountCBA(@BindBean final InternalTenantContext context);
 }
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.sql.stg b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
index ae19a63..c99d95c 100644
--- a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
@@ -85,4 +85,17 @@ getInvoiceItemsByParentInvoice() ::= <<
   <AND_CHECK_TENANT("items.")>
   <defaultOrderBy()>
   ;
->>
\ No newline at end of file
+>>
+
+getAccountCBA() ::= <<
+select coalesce(sum(ii.amount), 0) cba
+from invoice_items ii
+join invoices i on i.id = ii.invoice_id
+where i.status = 'COMMITTED'
+and ii.type = 'CBA_ADJ'
+and <accountRecordIdField("i.")> = :accountRecordId
+and <accountRecordIdField("ii.")> = :accountRecordId
+<AND_CHECK_TENANT("i.")>
+<AND_CHECK_TENANT("ii.")>
+;
+>>
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java
index 44d6178..1cb8bbf 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 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
@@ -813,7 +813,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         final UUID accountId = account.getId();
         final UUID bundleId = UUID.randomUUID();
 
-        createCredit(accountId, clock.getUTCToday(), new BigDecimal("20.0"));
+        final InvoiceItemModelDao credit = createCredit(accountId, clock.getUTCToday(), new BigDecimal("20.0"), true);
 
         final String description = UUID.randomUUID().toString();
         final InvoiceModelDao invoiceForExternalCharge = new InvoiceModelDao(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD, false);
@@ -821,8 +821,55 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         invoiceForExternalCharge.addInvoiceItem(externalCharge);
         final InvoiceItemModelDao charge = invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(invoiceForExternalCharge), context).get(0);
 
-        final InvoiceModelDao newInvoice = invoiceDao.getById(charge.getInvoiceId(), context);
-        final List<InvoiceItemModelDao> items = newInvoice.getInvoiceItems();
+        InvoiceModelDao newInvoice = invoiceDao.getById(charge.getInvoiceId(), context);
+        List<InvoiceItemModelDao> items = newInvoice.getInvoiceItems();
+        // No CBA consumed yet since the credit was created on a DRAFT invoice
+        assertEquals(items.size(), 1);
+        assertEquals(items.get(0).getType(), InvoiceItemType.EXTERNAL_CHARGE);
+        assertEquals(items.get(0).getDescription(), description);
+
+        invoiceDao.changeInvoiceStatus(credit.getInvoiceId(), InvoiceStatus.COMMITTED, context);
+
+        // CBA should have been consumed
+        newInvoice = invoiceDao.getById(charge.getInvoiceId(), context);
+        items = newInvoice.getInvoiceItems();
+        assertEquals(items.size(), 2);
+        for (final InvoiceItemModelDao cur : items) {
+            if (cur.getId().equals(charge.getId())) {
+                assertEquals(cur.getType(), InvoiceItemType.EXTERNAL_CHARGE);
+                assertEquals(cur.getDescription(), description);
+            } else {
+                assertEquals(cur.getType(), InvoiceItemType.CBA_ADJ);
+                assertTrue(cur.getAmount().compareTo(new BigDecimal("-15.00")) == 0);
+            }
+        }
+    }
+
+    @Test(groups = "slow")
+    public void testExternalChargeOnDRAFTInvoiceWithCBA() throws InvoiceApiException, EntityPersistenceException {
+        final UUID accountId = account.getId();
+        final UUID bundleId = UUID.randomUUID();
+
+        final InvoiceItemModelDao credit = createCredit(accountId, clock.getUTCToday(), new BigDecimal("20.0"), false);
+
+        final String description = UUID.randomUUID().toString();
+        final InvoiceModelDao draftInvoiceForExternalCharge = new InvoiceModelDao(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD, false, InvoiceStatus.DRAFT);
+        final InvoiceItemModelDao externalCharge = new InvoiceItemModelDao(new ExternalChargeInvoiceItem(draftInvoiceForExternalCharge.getId(), accountId, bundleId, description, clock.getUTCToday(), new BigDecimal("15.0"), Currency.USD));
+        draftInvoiceForExternalCharge.addInvoiceItem(externalCharge);
+        final InvoiceItemModelDao charge = invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(draftInvoiceForExternalCharge), context).get(0);
+
+        InvoiceModelDao newInvoice = invoiceDao.getById(charge.getInvoiceId(), context);
+        List<InvoiceItemModelDao> items = newInvoice.getInvoiceItems();
+        // No CBA consumed yet since the charge was created on a DRAFT invoice
+        assertEquals(items.size(), 1);
+        assertEquals(items.get(0).getType(), InvoiceItemType.EXTERNAL_CHARGE);
+        assertEquals(items.get(0).getDescription(), description);
+
+        invoiceDao.changeInvoiceStatus(charge.getInvoiceId(), InvoiceStatus.COMMITTED, context);
+
+        // CBA should have been consumed
+        newInvoice = invoiceDao.getById(charge.getInvoiceId(), context);
+        items = newInvoice.getInvoiceItems();
         assertEquals(items.size(), 2);
         for (final InvoiceItemModelDao cur : items) {
             if (cur.getId().equals(charge.getId())) {
@@ -924,13 +971,13 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
     }
 
     @Test(groups = "slow")
-    public void testAccountCredit() {
+    public void testAccountCredit() throws InvoiceApiException {
         final UUID accountId = account.getId();
         final LocalDate effectiveDate = new LocalDate(2011, 3, 1);
 
         final BigDecimal creditAmount = new BigDecimal("5.0");
 
-        createCredit(accountId, effectiveDate, creditAmount);
+        createCredit(accountId, effectiveDate, creditAmount, true);
 
         final List<InvoiceModelDao> invoices = invoiceDao.getAllInvoicesByAccount(context);
         assertEquals(invoices.size(), 1);
@@ -952,6 +999,12 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         }
         assertTrue(foundCredit);
         assertTrue(foundCBA);
+
+        // No account CBA yet since the invoice is in DRAFT mode
+        assertEquals(invoiceDao.getAccountCBA(accountId, context).compareTo(BigDecimal.ZERO), 0);
+
+        invoiceDao.changeInvoiceStatus(invoice.getId(), InvoiceStatus.COMMITTED, context);
+        assertEquals(invoiceDao.getAccountCBA(accountId, context).compareTo(creditAmount), 0);
     }
 
     @Test(groups = "slow")
@@ -999,7 +1052,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         // Create the credit item
         final LocalDate effectiveDate = new LocalDate(2011, 3, 1);
 
-        createCredit(accountId, invoice1.getId(), effectiveDate, creditAmount);
+        createCredit(accountId, invoice1.getId(), effectiveDate, creditAmount, false);
 
         final List<InvoiceModelDao> invoices = invoiceDao.getAllInvoicesByAccount(context);
         assertEquals(invoices.size(), 1);
@@ -1114,7 +1167,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         assertEquals(allInvoicesByAccount.size(), 1);
 
         // insert DRAFT invoice
-        createCredit(accountId, new LocalDate(2011, 12, 31), BigDecimal.TEN);
+        createCredit(accountId, new LocalDate(2011, 12, 31), BigDecimal.TEN, true);
 
         allInvoicesByAccount = invoiceDao.getInvoicesByAccount(new LocalDate(2011, 1, 1), context);
         assertEquals(allInvoicesByAccount.size(), 2);
@@ -1694,14 +1747,14 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
     }
 
 
-    private void createCredit(final UUID accountId, final LocalDate effectiveDate, final BigDecimal creditAmount) {
-        createCredit(accountId, null, effectiveDate, creditAmount);
+    private InvoiceItemModelDao createCredit(final UUID accountId, final LocalDate effectiveDate, final BigDecimal creditAmount, final boolean draft) {
+        return createCredit(accountId, null, effectiveDate, creditAmount, draft);
     }
 
-    private void createCredit(final UUID accountId, @Nullable final UUID invoiceId, final LocalDate effectiveDate, final BigDecimal creditAmount) {
+    private InvoiceItemModelDao createCredit(final UUID accountId, @Nullable final UUID invoiceId, final LocalDate effectiveDate, final BigDecimal creditAmount, final boolean draft) {
         final InvoiceModelDao invoiceModelDao;
         if (invoiceId == null) {
-            invoiceModelDao = new InvoiceModelDao(accountId, effectiveDate, effectiveDate, Currency.USD, false, InvoiceStatus.DRAFT);
+            invoiceModelDao = new InvoiceModelDao(accountId, effectiveDate, effectiveDate, Currency.USD, false, draft ? InvoiceStatus.DRAFT : InvoiceStatus.COMMITTED);
         } else {
             invoiceModelDao = invoiceDao.getById(invoiceId, context);
         }
@@ -1715,7 +1768,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
                                                                           creditAmount.negate(),
                                                                           invoiceModelDao.getCurrency());
         invoiceModelDao.addInvoiceItem(new InvoiceItemModelDao(invoiceItem));
-        invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(invoiceModelDao), context);
+        return invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(invoiceModelDao), context).get(0);
     }
 
     @Test(groups = "slow")
diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BlockingCalculator.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BlockingCalculator.java
index a03af67..cf7507e 100644
--- a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BlockingCalculator.java
+++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BlockingCalculator.java
@@ -131,7 +131,7 @@ public class BlockingCalculator {
                 }
 
                 final List<BlockingState> subscriptionBlockingEvents = perSubscriptionBlockingEvents.get(subscription.getId()) != null ? perSubscriptionBlockingEvents.get(subscription.getId()) : ImmutableList.<BlockingState>of();
-                final List<BlockingState> aggregateSubscriptionBlockingEvents = getAggregateBlockingEventsPerSubscription(subscriptionBlockingEvents, bundleBlockingEvents, accountBlockingEvents);
+                final List<BlockingState> aggregateSubscriptionBlockingEvents = getAggregateBlockingEventsPerSubscription(subscription.getEndDate(), subscriptionBlockingEvents, bundleBlockingEvents, accountBlockingEvents);
                 final List<DisabledDuration> accountBlockingDurations = createBlockingDurations(aggregateSubscriptionBlockingEvents);
 
                 billingEventsToAdd.addAll(createNewEvents(accountBlockingDurations, billingEvents, subscription, context));
@@ -150,9 +150,16 @@ public class BlockingCalculator {
         return !(billingEventsToAdd.isEmpty() && billingEventsToRemove.isEmpty());
     }
 
-    final List<BlockingState> getAggregateBlockingEventsPerSubscription(final Iterable<BlockingState> subscriptionBlockingEvents, final Iterable<BlockingState> bundleBlockingEvents, final Iterable<BlockingState> accountBlockingEvents) {
+    final List<BlockingState> getAggregateBlockingEventsPerSubscription(@Nullable final DateTime subscriptionEndDate, final Iterable<BlockingState> subscriptionBlockingEvents, final Iterable<BlockingState> bundleBlockingEvents, final Iterable<BlockingState> accountBlockingEvents) {
         final Iterable<BlockingState> tmp = Iterables.concat(subscriptionBlockingEvents, bundleBlockingEvents, accountBlockingEvents);
-        final List<BlockingState> result = Lists.newArrayList(tmp);
+        final Iterable<BlockingState> allEventsPriorToCancelDate = Iterables.filter(tmp,
+                                                                                    new Predicate<BlockingState>() {
+                                                                                        @Override
+                                                                                        public boolean apply(final BlockingState input) {
+                                                                                            return subscriptionEndDate == null || input.getEffectiveDate().compareTo(subscriptionEndDate) <= 0;
+                                                                                        }
+                                                                                    });
+        final List<BlockingState> result = Lists.newArrayList(allEventsPriorToCancelDate);
         Collections.sort(result);
         return result;
     }
diff --git a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
index 0fcfb7f..f6cd52a 100644
--- a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
+++ b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
@@ -496,7 +496,7 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
                                                                                                           (BigDecimal) row.get("amount"),
                                                                                                           Currency.valueOf((String) row.get("currency")),
                                                                                                           (String) row.get("gateway_error_code"),
-                                                                                                          (String) row.get("gateway_error_msg"));
+                                                                                                          String.valueOf(row.get("gateway_error_msg")));
                     result.add(transactionModelDao);
                 }
                 return result;
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java
index d70c150..9ee931b 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java
@@ -776,7 +776,7 @@ public class TestInvoice extends TestJaxrsBase {
         credit.setCreditAmount(creditAmount);
 
         // insert credit to child account
-        final Credit creditJson = killBillClient.createCredit(credit, false, createdBy, reason, comment);
+        final Credit creditJson = killBillClient.createCredit(credit, true, requestOptions);
 
         Invoices childInvoices = killBillClient.getInvoicesForAccount(childAccount.getAccountId(), true, false);
         Assert.assertEquals(childInvoices.size(), 1);