killbill-memoizeit

Merge remote-tracking branch 'killbill/work-for-release-0.19.x'

2/13/2018 5:54:48 PM

Changes

.travis.yml 37(+0 -37)

NEWS 8(+7 -1)

Details

diff --git a/.circleci/config.yml b/.circleci/config.yml
index ca7b9fc..ccde348 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -174,46 +174,18 @@ workflows:
   version: 2
   build-and-test:
     jobs:
-      - build:
-          filters:
-            branches:
-              only:
-                - master
-                - work-for-release-0.19.x
-                - circle-ci-experiment
+      - build
       - test-h2:
           requires:
             - build
-          filters:
-            branches:
-              only:
-                - master
-                - circle-ci-experiment
       - test-mysql:
           requires:
             - build
-          filters:
-            branches:
-              only:
-                - master
-                - work-for-release-0.19.x
-                - circle-ci-experiment
       - test-postgresql:
           requires:
             - build
-          filters:
-            branches:
-              only:
-                - master
-                - circle-ci-experiment
       - integration-tests:
           requires:
             - test-h2
             - test-mysql
             - test-postgresql
-          filters:
-            branches:
-              only:
-                - master
-                - work-for-release-0.19.x
-                - circle-ci-experiment
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
index 808e0c5..c812bfe 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
@@ -352,10 +352,11 @@ public class InvoiceDispatcher {
                     }
                 }
             } else /* Dry run use cases */ {
-
                 final NotificationQueue notificationQueue = notificationQueueService.getNotificationQueue(DefaultInvoiceService.INVOICE_SERVICE_NAME,
                                                                                                           DefaultNextBillingDateNotifier.NEXT_BILLING_DATE_NOTIFIER_QUEUE);
-                final Iterable<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotifications = notificationQueue.getFutureNotificationForSearchKeys(context.getAccountRecordId(), context.getTenantRecordId());
+                final Iterable<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotificationsIterable = notificationQueue.getFutureNotificationForSearchKeys(context.getAccountRecordId(), context.getTenantRecordId());
+                // Copy the results as retrieving the iterator will issue a query each time. This also makes sure the underlying JDBC connection is closed.
+                final List<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotifications = ImmutableList.<NotificationEventWithMetadata<NextBillingDateNotificationKey>>copyOf(futureNotificationsIterable);
 
                 final Map<UUID, DateTime> nextScheduledSubscriptionsEventMap = getNextTransitionsForSubscriptions(billingEvents);
 
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDatePoster.java b/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDatePoster.java
index fb269c5..6cfcb83 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDatePoster.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDatePoster.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -19,6 +19,7 @@
 package org.killbill.billing.invoice.notification;
 
 import java.io.IOException;
+import java.util.Iterator;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
@@ -84,19 +85,27 @@ public class DefaultNextBillingDatePoster implements NextBillingDatePoster {
             final Iterable<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotifications = nextBillingQueue.getFutureNotificationFromTransactionForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId(), entitySqlDaoWrapperFactory.getHandle().getConnection());
 
             NotificationEventWithMetadata<NextBillingDateNotificationKey> existingNotificationForEffectiveDate = null;
-            for (final NotificationEventWithMetadata<NextBillingDateNotificationKey> input : futureNotifications) {
-                final boolean isEventDryRunForNotifications = input.getEvent().isDryRunForInvoiceNotification() != null ?
-                                                              input.getEvent().isDryRunForInvoiceNotification() : false;
-
-                final LocalDate notificationEffectiveLocaleDate = internalCallContext.toLocalDate(futureNotificationTime);
-                final LocalDate eventEffectiveLocaleDate = internalCallContext.toLocalDate(input.getEffectiveDate());
-
-                if (notificationEffectiveLocaleDate.compareTo(eventEffectiveLocaleDate) == 0 &&
-                    ((isDryRunForInvoiceNotification && isEventDryRunForNotifications) ||
-                     (!isDryRunForInvoiceNotification && !isEventDryRunForNotifications))) {
-                    existingNotificationForEffectiveDate = input;
+            final Iterator<NotificationEventWithMetadata<NextBillingDateNotificationKey>> iterator = futureNotifications.iterator();
+            try {
+                while (iterator.hasNext()) {
+                    final NotificationEventWithMetadata<NextBillingDateNotificationKey> input = iterator.next();
+                    final boolean isEventDryRunForNotifications = input.getEvent().isDryRunForInvoiceNotification() != null ?
+                                                                  input.getEvent().isDryRunForInvoiceNotification() : false;
+
+                    final LocalDate notificationEffectiveLocaleDate = internalCallContext.toLocalDate(futureNotificationTime);
+                    final LocalDate eventEffectiveLocaleDate = internalCallContext.toLocalDate(input.getEffectiveDate());
+
+                    if (notificationEffectiveLocaleDate.compareTo(eventEffectiveLocaleDate) == 0 &&
+                        ((isDryRunForInvoiceNotification && isEventDryRunForNotifications) ||
+                         (!isDryRunForInvoiceNotification && !isEventDryRunForNotifications))) {
+                        existingNotificationForEffectiveDate = input;
+                    }
                 }
+            } finally {
                 // Go through all results to close the connection
+                while (iterator.hasNext()) {
+                    iterator.next();
+                }
             }
 
             if (existingNotificationForEffectiveDate == null) {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/notification/ParentInvoiceCommitmentPoster.java b/invoice/src/main/java/org/killbill/billing/invoice/notification/ParentInvoiceCommitmentPoster.java
index 4a5d7e9..b8c7d76 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/notification/ParentInvoiceCommitmentPoster.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/notification/ParentInvoiceCommitmentPoster.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -18,6 +18,7 @@
 package org.killbill.billing.invoice.notification;
 
 import java.io.IOException;
+import java.util.Iterator;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
@@ -58,17 +59,22 @@ public class ParentInvoiceCommitmentPoster {
             final Iterable<NotificationEventWithMetadata<ParentInvoiceCommitmentNotificationKey>> futureNotifications = commitInvoiceQueue.getFutureNotificationFromTransactionForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId(), entitySqlDaoWrapperFactory.getHandle().getConnection());
 
             boolean existingFutureNotificationWithSameDateAndInvoiceId = false;
-            for (final NotificationEventWithMetadata<ParentInvoiceCommitmentNotificationKey> input : futureNotifications) {
-
-
-
-                final LocalDate notificationEffectiveLocaleDate = internalCallContext.toLocalDate(futureNotificationTime);
-                final LocalDate eventEffectiveLocaleDate = internalCallContext.toLocalDate(input.getEffectiveDate());
-
-                if (notificationEffectiveLocaleDate.compareTo(eventEffectiveLocaleDate) == 0 && input.getEvent().getUuidKey().equals(invoiceId)) {
-                    existingFutureNotificationWithSameDateAndInvoiceId = true;
+            final Iterator<NotificationEventWithMetadata<ParentInvoiceCommitmentNotificationKey>> iterator = futureNotifications.iterator();
+            try {
+                while (iterator.hasNext()) {
+                    final NotificationEventWithMetadata<ParentInvoiceCommitmentNotificationKey> input = iterator.next();
+                    final LocalDate notificationEffectiveLocaleDate = internalCallContext.toLocalDate(futureNotificationTime);
+                    final LocalDate eventEffectiveLocaleDate = internalCallContext.toLocalDate(input.getEffectiveDate());
+
+                    if (notificationEffectiveLocaleDate.compareTo(eventEffectiveLocaleDate) == 0 && input.getEvent().getUuidKey().equals(invoiceId)) {
+                        existingFutureNotificationWithSameDateAndInvoiceId = true;
+                    }
                 }
+            } finally {
                 // Go through all results to close the connection
+                while (iterator.hasNext()) {
+                    iterator.next();
+                }
             }
 
             if (!existingFutureNotificationWithSameDateAndInvoiceId) {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalCapacityUsageInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalCapacityUsageInArrear.java
new file mode 100644
index 0000000..e5321fe
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalCapacityUsageInArrear.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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.invoice.usage;
+
+import java.math.BigDecimal;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.catalog.api.Limit;
+import org.killbill.billing.catalog.api.Tier;
+import org.killbill.billing.catalog.api.Usage;
+import org.killbill.billing.invoice.api.InvoiceItem;
+import org.killbill.billing.invoice.model.UsageInvoiceItem;
+import org.killbill.billing.invoice.usage.details.UsageCapacityInArrearDetail;
+import org.killbill.billing.invoice.usage.details.UsageInArrearDetail;
+import org.killbill.billing.invoice.usage.details.UsageInArrearTierUnitDetail;
+import org.killbill.billing.usage.RawUsage;
+import org.killbill.billing.usage.api.RolledUpUnit;
+import org.killbill.billing.util.config.definition.InvoiceConfig.UsageDetailMode;
+import org.killbill.billing.util.jackson.ObjectMapper;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+
+import static org.killbill.billing.invoice.usage.UsageUtils.getCapacityInArrearTier;
+
+public class ContiguousIntervalCapacityUsageInArrear extends ContiguousIntervalUsageInArrear {
+
+    private static final Joiner joiner = Joiner.on(", ");
+
+    private static final ObjectMapper objectMapper = new ObjectMapper();
+
+    public ContiguousIntervalCapacityUsageInArrear(final Usage usage,
+                                                   final UUID accountId,
+                                                   final UUID invoiceId,
+                                                   final List<RawUsage> rawSubscriptionUsage,
+                                                   final LocalDate targetDate,
+                                                   final LocalDate rawUsageStartDate,
+                                                   final UsageDetailMode usageDetailMode,
+                                                   final InternalTenantContext internalTenantContext) {
+        super(usage, accountId, invoiceId, rawSubscriptionUsage, targetDate, rawUsageStartDate, usageDetailMode, internalTenantContext);
+    }
+
+
+
+    @Override
+    protected void populateResults(final LocalDate startDate, final LocalDate endDate, final Iterable<InvoiceItem> billedItems, final BigDecimal billedUsage, final BigDecimal toBeBilledUsage, final UsageInArrearDetail toBeBilledUsageDetails, final boolean areAllBilledItemsWithDetails, final List<InvoiceItem> result) {
+        // Compute final amount by subtracting  amount that was already billed.
+        if (!billedItems.iterator().hasNext() || billedUsage.compareTo(toBeBilledUsage) < 0) {
+            final BigDecimal amountToBill = toBeBilledUsage.subtract(billedUsage);
+
+            if (amountToBill.compareTo(BigDecimal.ZERO) > 0) {
+                    final String itemDetails = areAllBilledItemsWithDetails ? toJson(toBeBilledUsageDetails) : null;
+                    final InvoiceItem item = new UsageInvoiceItem(invoiceId, accountId, getBundleId(), getSubscriptionId(), getPlanName(),
+                                                                  getPhaseName(), usage.getName(), startDate, endDate, amountToBill, null, getCurrency(), null, itemDetails);
+                    result.add(item);
+            }
+        }
+
+    }
+
+
+    @Override
+    protected UsageInArrearDetail getToBeBilledUsageDetails(final List<RolledUpUnit> rolledUpUnits, final Iterable<InvoiceItem> billedItems, final boolean areAllBilledItemsWithDetails) throws CatalogApiException {
+        return computeToBeBilledCapacityInArrear(rolledUpUnits);
+    }
+
+    private Limit getTierLimit(final Tier tier, final String unitType) {
+        for (final Limit cur : tier.getLimits()) {
+            if (cur.getUnit().getName().equals(unitType)) {
+                return cur;
+            }
+        }
+        Preconditions.checkState(false, "Could not find unit type " + unitType + " in usage tier ");
+        return null;
+    }
+
+    @VisibleForTesting
+    UsageCapacityInArrearDetail computeToBeBilledCapacityInArrear(final List<RolledUpUnit> roUnits) throws CatalogApiException {
+        Preconditions.checkState(isBuilt.get());
+
+        final List<Tier> tiers = getCapacityInArrearTier(usage);
+
+        final Set<String> perUnitTypeDetailTierLevel = new HashSet<String>();
+        int tierNum = 0;
+        final List<UsageInArrearTierUnitDetail> toBeBilledDetails = Lists.newLinkedList();
+        for (final Tier cur : tiers) {
+            tierNum++;
+            boolean complies = true;
+            for (final RolledUpUnit ro : roUnits) {
+                final Limit tierLimit = getTierLimit(cur, ro.getUnitType());
+                // We ignore the min and only look at the max Limit as the tiers should be contiguous.
+                // Specifying a -1 value for last max tier will make the validation works
+                if (tierLimit.getMax() != (double) -1 && ro.getAmount().doubleValue() > tierLimit.getMax()) {
+                    complies = false;
+                } else {
+                    if (!perUnitTypeDetailTierLevel.contains(ro.getUnitType())) {
+                        toBeBilledDetails.add(new UsageInArrearTierUnitDetail(tierNum, ro.getUnitType(), cur.getRecurringPrice().getPrice(getCurrency()), ro.getAmount().intValue()));
+                        perUnitTypeDetailTierLevel.add(ro.getUnitType());
+                    }
+                }
+            }
+            if (complies) {
+                return new UsageCapacityInArrearDetail(toBeBilledDetails, cur.getRecurringPrice().getPrice(getCurrency()));
+            }
+        }
+        // Probably invalid catalog config
+        joiner.join(roUnits);
+        Preconditions.checkState(false, "Could not find tier for usage " + usage.getName() + "matching with data = " + joiner.join(roUnits));
+        return null;
+    }
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableUsageInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableUsageInArrear.java
new file mode 100644
index 0000000..04073ba
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableUsageInArrear.java
@@ -0,0 +1,248 @@
+/*
+ * 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.invoice.usage;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.catalog.api.TieredBlock;
+import org.killbill.billing.catalog.api.Usage;
+import org.killbill.billing.invoice.api.InvoiceItem;
+import org.killbill.billing.invoice.model.UsageInvoiceItem;
+import org.killbill.billing.invoice.usage.details.UsageConsumableInArrearDetail;
+import org.killbill.billing.invoice.usage.details.UsageConsumableInArrearTierUnitDetail;
+import org.killbill.billing.invoice.usage.details.UsageInArrearDetail;
+import org.killbill.billing.usage.RawUsage;
+import org.killbill.billing.usage.api.RolledUpUnit;
+import org.killbill.billing.util.config.definition.InvoiceConfig.UsageDetailMode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+
+import static org.killbill.billing.invoice.usage.UsageUtils.getConsumableInArrearTieredBlocks;
+
+public class ContiguousIntervalConsumableUsageInArrear extends ContiguousIntervalUsageInArrear {
+
+    private static final Logger log = LoggerFactory.getLogger(ContiguousIntervalConsumableUsageInArrear.class);
+
+    public ContiguousIntervalConsumableUsageInArrear(final Usage usage,
+                                                     final UUID accountId,
+                                                     final UUID invoiceId,
+                                                     final List<RawUsage> rawSubscriptionUsage,
+                                                     final LocalDate targetDate,
+                                                     final LocalDate rawUsageStartDate,
+                                                     final UsageDetailMode usageDetailMode,
+                                                     final InternalTenantContext internalTenantContext) {
+        super(usage, accountId, invoiceId, rawSubscriptionUsage, targetDate, rawUsageStartDate, usageDetailMode, internalTenantContext);
+    }
+
+    @Override
+    protected void populateResults(final LocalDate startDate, final LocalDate endDate, final Iterable<InvoiceItem> billedItems, final BigDecimal billedUsage, final BigDecimal toBeBilledUsage, final UsageInArrearDetail toBeBilledUsageDetails, final boolean areAllBilledItemsWithDetails, final List<InvoiceItem> result) {
+        // Compute final amount by subtracting  amount that was already billed.
+        if (!billedItems.iterator().hasNext() || billedUsage.compareTo(toBeBilledUsage) < 0) {
+            // In the case past invoice items showed the details (areAllBilledItemsWithDetails=true), billed usage has already been taken into account
+            // as it part of the reconciliation logic, so no need to subtract it here
+            final BigDecimal amountToBill = areAllBilledItemsWithDetails ? toBeBilledUsage : toBeBilledUsage.subtract(billedUsage);
+
+            if (amountToBill.compareTo(BigDecimal.ZERO) > 0) {
+                if (UsageDetailMode.DETAIL == usageDetailMode) {
+                    for (UsageConsumableInArrearTierUnitDetail toBeBilledUsageDetail : ((UsageConsumableInArrearDetail) toBeBilledUsageDetails).getTierDetails()) {
+                        final InvoiceItem item = new UsageInvoiceItem(invoiceId, accountId, getBundleId(), getSubscriptionId(), getPlanName(),
+                                                                      getPhaseName(), usage.getName(), startDate, endDate, toBeBilledUsageDetail.getAmount(), toBeBilledUsageDetail.getTierPrice(), getCurrency(), toBeBilledUsageDetail.getQuantity(), null);
+                        result.add(item);
+                    }
+                } else {
+                    final String itemDetails = toJson(toBeBilledUsageDetails);
+                    final InvoiceItem item = new UsageInvoiceItem(invoiceId, accountId, getBundleId(), getSubscriptionId(), getPlanName(),
+                                                                  getPhaseName(), usage.getName(), startDate, endDate, amountToBill, null, getCurrency(), null, itemDetails);
+                    result.add(item);
+                }
+            }
+        }
+
+    }
+
+    @Override
+    protected UsageInArrearDetail getToBeBilledUsageDetails(final List<RolledUpUnit> rolledUpUnits, final Iterable<InvoiceItem> billedItems, final boolean areAllBilledItemsWithDetails) throws CatalogApiException {
+
+        final Map<String, List<UsageConsumableInArrearTierUnitDetail>> previousUnitsUsage;
+        if (areAllBilledItemsWithDetails) {
+            previousUnitsUsage = new HashMap<String, List<UsageConsumableInArrearTierUnitDetail>>();
+            for (RolledUpUnit cur : rolledUpUnits) {
+                final List<UsageConsumableInArrearTierUnitDetail> usageInArrearDetailForUnitType = getUsageConsumableInArrearDetailForUnitType(billedItems, cur.getUnitType());
+                previousUnitsUsage.put(cur.getUnitType(), usageInArrearDetailForUnitType);
+            }
+        } else {
+            previousUnitsUsage = ImmutableMap.of();
+        }
+
+        final List<UsageConsumableInArrearTierUnitDetail> usageConsumableInArrearTierUnitDetails = new ArrayList<UsageConsumableInArrearTierUnitDetail>();
+        for (final RolledUpUnit cur : rolledUpUnits) {
+            if (!unitTypes.contains(cur.getUnitType())) {
+                log.warn("ContiguousIntervalConsumableInArrear is skipping unitType " + cur.getUnitType());
+                continue;
+            }
+            final List<UsageConsumableInArrearTierUnitDetail> previousUsage = previousUnitsUsage.containsKey(cur.getUnitType()) ? previousUnitsUsage.get(cur.getUnitType()) : ImmutableList.<UsageConsumableInArrearTierUnitDetail>of();
+
+            usageConsumableInArrearTierUnitDetails.addAll(computeToBeBilledConsumableInArrear(cur, previousUsage, areAllBilledItemsWithDetails));
+        }
+        final UsageInArrearDetail toBeBilledUsageDetails = new UsageConsumableInArrearDetail(usageConsumableInArrearTierUnitDetails);
+        return toBeBilledUsageDetails;
+    }
+
+    private List<UsageConsumableInArrearTierUnitDetail> getUsageConsumableInArrearDetailForUnitType(final Iterable<InvoiceItem> billedItems, final String unitType) {
+
+        final List<UsageConsumableInArrearTierUnitDetail> result = new ArrayList<UsageConsumableInArrearTierUnitDetail>();
+        for (final InvoiceItem bi : billedItems) {
+
+            final UsageConsumableInArrearDetail usageDetail = fromJson(bi.getItemDetails());
+            for (final UsageConsumableInArrearTierUnitDetail curDetail : usageDetail.getTierDetails()) {
+                if (curDetail.getTierUnit().equals(unitType)) {
+                    result.add(curDetail);
+                }
+            }
+        }
+        return result;
+    }
+
+    @VisibleForTesting
+    List<UsageConsumableInArrearTierUnitDetail> computeToBeBilledConsumableInArrear(final RolledUpUnit roUnit, final List<UsageConsumableInArrearTierUnitDetail> previousUsage, final boolean areAllBilledItemsWithDetails) throws CatalogApiException {
+
+        Preconditions.checkState(isBuilt.get());
+        final List<TieredBlock> tieredBlocks = getConsumableInArrearTieredBlocks(usage, roUnit.getUnitType());
+
+        switch (usage.getTierBlockPolicy()) {
+            case ALL_TIERS:
+                return computeToBeBilledConsumableInArrearWith_ALL_TIERS(tieredBlocks, previousUsage, roUnit.getAmount());
+            case TOP_TIER:
+                return Arrays.asList(computeToBeBilledConsumableInArrearWith_TOP_TIER(tieredBlocks, previousUsage, roUnit.getAmount(), areAllBilledItemsWithDetails));
+            default:
+                throw new IllegalStateException("Unknown TierBlockPolicy " + usage.getTierBlockPolicy());
+        }
+    }
+
+    List<UsageConsumableInArrearTierUnitDetail> computeToBeBilledConsumableInArrearWith_ALL_TIERS(final List<TieredBlock> tieredBlocks, final List<UsageConsumableInArrearTierUnitDetail> previousUsage, final Long units) throws CatalogApiException {
+
+        List<UsageConsumableInArrearTierUnitDetail> toBeBilledDetails = Lists.newLinkedList();
+        int remainingUnits = units.intValue();
+        int tierNum = 0;
+
+        final int lastPreviousUsageTier = previousUsage.size(); // we count tier from 1, 2, ...
+        final boolean hasPreviousUsage = lastPreviousUsageTier > 0;
+
+        for (final TieredBlock tieredBlock : tieredBlocks) {
+
+            tierNum++;
+            final int blockTierSize = tieredBlock.getSize().intValue();
+            final int tmp = remainingUnits / blockTierSize + (remainingUnits % blockTierSize == 0 ? 0 : 1);
+            int nbUsedTierBlocks;
+            if (tmp > tieredBlock.getMax()) {
+                nbUsedTierBlocks = tieredBlock.getMax().intValue();
+                remainingUnits -= tieredBlock.getMax() * blockTierSize;
+            } else {
+                nbUsedTierBlocks = tmp;
+                remainingUnits = 0;
+            }
+
+            if (nbUsedTierBlocks > 0) {
+                if (hasPreviousUsage) {
+                    final Integer previousUsageQuantity = tierNum <= lastPreviousUsageTier ? previousUsage.get(tierNum - 1).getQuantity() : 0;
+                    if (tierNum < lastPreviousUsageTier) {
+                        Preconditions.checkState(nbUsedTierBlocks == previousUsageQuantity, String.format("Expected usage for tier='%d', unit='%s' to be full, instead found units='[%d/%d]'",
+                                                                                                          tierNum, tieredBlock.getUnit().getName(), nbUsedTierBlocks, previousUsageQuantity));
+                    } else {
+                        Preconditions.checkState(nbUsedTierBlocks - previousUsageQuantity >= 0, String.format("Expected usage for tier='%d', unit='%s' to contain at least as mush as current usage, instead found units='[%d/%d]",
+                                                                                                              tierNum, tieredBlock.getUnit().getName(), nbUsedTierBlocks, previousUsageQuantity));
+                    }
+                    nbUsedTierBlocks = nbUsedTierBlocks - previousUsageQuantity;
+                }
+                if (nbUsedTierBlocks > 0) {
+                    toBeBilledDetails.add(new UsageConsumableInArrearTierUnitDetail(tierNum, tieredBlock.getUnit().getName(), tieredBlock.getPrice().getPrice(getCurrency()), blockTierSize, nbUsedTierBlocks));
+                }
+            }
+        }
+        return toBeBilledDetails;
+    }
+
+    UsageConsumableInArrearTierUnitDetail computeToBeBilledConsumableInArrearWith_TOP_TIER(final List<TieredBlock> tieredBlocks, final List<UsageConsumableInArrearTierUnitDetail> previousUsage, final Long units, final boolean areAllBilledItemsWithDetails) throws CatalogApiException {
+
+        int remainingUnits = units.intValue();
+
+        // By default last last tierBlock
+        TieredBlock targetBlock = tieredBlocks.get(tieredBlocks.size() - 1);
+        int targetTierNum = tieredBlocks.size();
+        int tierNum = 0;
+        // Loop through all tier block
+        for (final TieredBlock tieredBlock : tieredBlocks) {
+
+            tierNum++;
+            final int blockTierSize = tieredBlock.getSize().intValue();
+            final int tmp = remainingUnits / blockTierSize + (remainingUnits % blockTierSize == 0 ? 0 : 1);
+            if (tmp > tieredBlock.getMax()) {
+                remainingUnits -= tieredBlock.getMax() * blockTierSize;
+            } else {
+                targetBlock = tieredBlock;
+                targetTierNum = tierNum;
+                break;
+            }
+        }
+        final int lastBlockTierSize = targetBlock.getSize().intValue();
+        final int nbBlocks = units.intValue() / lastBlockTierSize + (units.intValue() % lastBlockTierSize == 0 ? 0 : 1);
+
+        return new UsageConsumableInArrearTierUnitDetail(targetTierNum, targetBlock.getUnit().getName(), targetBlock.getPrice().getPrice(getCurrency()), targetBlock.getSize().intValue(), nbBlocks);
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("ContiguousIntervalConsumableUsageInArrear{");
+        sb.append("transitionTimes=").append(transitionTimes);
+        sb.append(", billingEvents=").append(billingEvents);
+        sb.append(", rawSubscriptionUsage=").append(rawSubscriptionUsage);
+        sb.append(", rawUsageStartDate=").append(rawUsageStartDate);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    public static UsageConsumableInArrearDetail fromJson(String itemDetails) {
+        UsageConsumableInArrearDetail result = null;
+        if (itemDetails != null) {
+            try {
+                result = objectMapper.readValue(itemDetails, new TypeReference<UsageConsumableInArrearDetail>() {});
+            } catch (IOException e) {
+                Preconditions.checkState(false, e.getMessage());
+            }
+        }
+        return result;
+    }
+
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalUsageInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalUsageInArrear.java
index b49d684..7dfba4f 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalUsageInArrear.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalUsageInArrear.java
@@ -17,10 +17,8 @@
 
 package org.killbill.billing.invoice.usage;
 
-import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -32,69 +30,61 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import javax.annotation.Nullable;
 
 import org.joda.time.LocalDate;
+import org.killbill.billing.ErrorCode;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.catalog.api.BillingMode;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.Currency;
-import org.killbill.billing.catalog.api.Limit;
-import org.killbill.billing.catalog.api.Tier;
-import org.killbill.billing.catalog.api.TieredBlock;
 import org.killbill.billing.catalog.api.Usage;
 import org.killbill.billing.catalog.api.UsageType;
+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.generator.BillingIntervalDetail;
 import org.killbill.billing.invoice.model.UsageInvoiceItem;
+import org.killbill.billing.invoice.usage.details.UsageInArrearDetail;
 import org.killbill.billing.junction.BillingEvent;
 import org.killbill.billing.usage.RawUsage;
 import org.killbill.billing.usage.api.RolledUpUnit;
 import org.killbill.billing.usage.api.RolledUpUsage;
 import org.killbill.billing.util.config.definition.InvoiceConfig.UsageDetailMode;
+import org.killbill.billing.util.jackson.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 
-import static org.killbill.billing.invoice.usage.UsageUtils.getCapacityInArrearTier;
 import static org.killbill.billing.invoice.usage.UsageUtils.getCapacityInArrearUnitTypes;
-import static org.killbill.billing.invoice.usage.UsageUtils.getConsumableInArrearTieredBlocks;
 import static org.killbill.billing.invoice.usage.UsageUtils.getConsumableInArrearUnitTypes;
 
-
-
 /**
  * There is one such class per subscriptionId, matching a given in arrear/consumable usage section and
  * referenced through a contiguous list of billing events.
  */
-public class ContiguousIntervalUsageInArrear {
+public abstract class ContiguousIntervalUsageInArrear {
 
     private static final Logger log = LoggerFactory.getLogger(ContiguousIntervalUsageInArrear.class);
 
-    private final List<LocalDate> transitionTimes;
-    private final List<BillingEvent> billingEvents;
-
-    private final Usage usage;
-    private final Set<String> unitTypes;
-    private final List<RawUsage> rawSubscriptionUsage;
-    private final LocalDate targetDate;
-    private final UUID accountId;
-    private final UUID invoiceId;
-    private final AtomicBoolean isBuilt;
-    private final LocalDate rawUsageStartDate;
-    private final InternalTenantContext internalTenantContext;
-    private final UsageDetailMode usageDetailMode;
-    private static final ObjectMapper objectMapper = new ObjectMapper();
+    protected final List<LocalDate> transitionTimes;
+    protected final List<BillingEvent> billingEvents;
+
+    protected final Usage usage;
+    protected final Set<String> unitTypes;
+    protected final List<RawUsage> rawSubscriptionUsage;
+    protected final LocalDate targetDate;
+    protected final UUID accountId;
+    protected final UUID invoiceId;
+    protected final AtomicBoolean isBuilt;
+    protected final LocalDate rawUsageStartDate;
+    protected final InternalTenantContext internalTenantContext;
+    protected final UsageDetailMode usageDetailMode;
+    protected static final ObjectMapper objectMapper = new ObjectMapper();
 
     public ContiguousIntervalUsageInArrear(final Usage usage,
                                            final UUID accountId,
@@ -171,7 +161,7 @@ public class ContiguousIntervalUsageInArrear {
      * @param existingUsage existing on disk usage items for the subscription
      * @throws CatalogApiException
      */
-    public UsageInArrearItemsAndNextNotificationDate computeMissingItemsAndNextNotificationDate(final List<InvoiceItem> existingUsage) throws CatalogApiException {
+    public UsageInArrearItemsAndNextNotificationDate computeMissingItemsAndNextNotificationDate(final List<InvoiceItem> existingUsage) throws CatalogApiException, InvoiceApiException {
 
         Preconditions.checkState(isBuilt.get());
 
@@ -194,54 +184,54 @@ public class ContiguousIntervalUsageInArrear {
         }
 
         final List<RolledUpUsage> allUsage = getRolledUpUsage();
+        // Each RolledUpUsage 'ru' is for a specific time period and across all units
         for (final RolledUpUsage ru : allUsage) {
 
-            List<UsageInArrearDetail> toBeBilledUsageDetails = Lists.newLinkedList();
-            BigDecimal toBeBilledUsage = BigDecimal.ZERO;
-            if (usage.getUsageType() == UsageType.CAPACITY) {
-                toBeBilledUsageDetails.addAll(computeToBeBilledCapacityInArrear(ru.getRolledUpUnits()));
-            } else /* UsageType.CONSUMABLE */{
-
-                // Compute total price amount that should be billed for that period of time (and usage section) across unitTypes.
-                for (final RolledUpUnit cur : ru.getRolledUpUnits()) {
-                    if (!unitTypes.contains(cur.getUnitType())) {
-                        log.warn("ContiguousIntervalConsumableInArrear is skipping unitType " + cur.getUnitType());
-                        continue;
-                    }
-
-                    toBeBilledUsageDetails.addAll(computeToBeBilledConsumableInArrear(cur));
-                }
-
-            }
-            toBeBilledUsage = toBeBilledForUnit(toBeBilledUsageDetails);
-
-            // Retrieves current price amount billed for that period of time (and usage section)
+            //
+            // Previously billed items:
+            //
+            // 1. Retrieves current price amount billed for that period of time (and usage section)
             final Iterable<InvoiceItem> billedItems = getBilledItems(ru.getStart(), ru.getEnd(), existingUsage);
+            // 2. Verify whether previously built items have the item_details section
+            final boolean areAllBilledItemsWithDetails = areAllBilledItemsWithDetails(billedItems);
+            // 3. Computes total billed usage amount
             final BigDecimal billedUsage = computeBilledUsage(billedItems);
 
-            // Compare the two and add the missing piece if required.
-            if (!billedItems.iterator().hasNext() || billedUsage.compareTo(toBeBilledUsage) < 0) {
-                toBeBilledUsageDetails = reconcileExistedBilledWithToBeBilled(billedItems, toBeBilledUsageDetails);
-                final BigDecimal amountToBill = toBeBilledForUnit(toBeBilledUsageDetails);
-
-                if (amountToBill.compareTo(BigDecimal.ZERO) > 0) {
-                    if (UsageDetailMode.DETAIL == usageDetailMode && usage.getUsageType() == UsageType.CONSUMABLE){
-                        for (UsageInArrearDetail toBeBilledUsageDetail : toBeBilledUsageDetails){
-                            final InvoiceItem item = new UsageInvoiceItem(invoiceId, accountId, getBundleId(), getSubscriptionId(), getPlanName(),
-                                                                          getPhaseName(), usage.getName(), ru.getStart(), ru.getEnd(), toBeBilledUsageDetail.getAmount(), toBeBilledUsageDetail.getTierPrice(), getCurrency(),toBeBilledUsageDetail.getQuantity(),null);
-                            result.add(item);
-                        }
-                    } else {
-                        final InvoiceItem item = new UsageInvoiceItem(invoiceId, accountId, getBundleId(), getSubscriptionId(), getPlanName(),
-                                                                      getPhaseName(), usage.getName(), ru.getStart(), ru.getEnd(), amountToBill, null, getCurrency(),null, toJson(toBeBilledUsageDetails));
-                        result.add(item);
-                    }
-                }
+            final List<RolledUpUnit> rolledUpUnits = ru.getRolledUpUnits();
+
+            final UsageInArrearDetail toBeBilledUsageDetails = getToBeBilledUsageDetails(rolledUpUnits, billedItems, areAllBilledItemsWithDetails);
+            final BigDecimal toBeBilledUsage = toBeBilledUsageDetails.getAmount();
+
+            final int billedUsageDelta = billedUsage.compareTo(toBeBilledUsage);
+            // We built more in the past than what we find now, data usage vanished?
+            if (billedUsageDelta > 0) {
+                /*
+                throw new InvoiceApiException(ErrorCode.UNEXPECTED_ERROR,
+                                              String.format("ILLEGAL INVOICING STATE: Usage period start='%s', end='%s', previously billed amount='%.2f', new proposed amount='%.2f'",
+                                                            ru.getStart(), ru.getEnd(), billedUsage, toBeBilledUsage));
+               */
+                // Something remains to be billed
+            } else if (billedUsageDelta < 0) {
+                populateResults(ru.getStart(), ru.getEnd(), billedItems, billedUsage, toBeBilledUsage, toBeBilledUsageDetails, areAllBilledItemsWithDetails, result);
             }
         }
+        final LocalDate nextNotificationDate = computeNextNotificationDate();
+        return new UsageInArrearItemsAndNextNotificationDate(result, nextNotificationDate);
+    }
+
+    protected abstract void populateResults(final LocalDate startDate, final LocalDate endDate, final Iterable<InvoiceItem> billedItems, final BigDecimal billedUsage, final BigDecimal toBeBilledUsage, final UsageInArrearDetail toBeBilledUsageDetails, final boolean areAllBilledItemsWithDetails, final List<InvoiceItem> result);
+
+
+    protected abstract UsageInArrearDetail getToBeBilledUsageDetails(final List<RolledUpUnit> rolledUpUnits, final Iterable<InvoiceItem> billedItems, final boolean areAllBilledItemsWithDetails) throws CatalogApiException;
 
-        final LocalDate nextNotificationdate = computeNextNotificationDate();
-        return new UsageInArrearItemsAndNextNotificationDate(result, nextNotificationdate);
+    private boolean areAllBilledItemsWithDetails(final Iterable<InvoiceItem> billedItems) {
+        boolean atLeastOneItemWithoutDetails = Iterables.any(billedItems, new Predicate<InvoiceItem>() {
+            @Override
+            public boolean apply(final InvoiceItem input) {
+                return input.getItemDetails() == null || input.getItemDetails().isEmpty();
+            }
+        });
+        return !atLeastOneItemWithoutDetails;
     }
 
     private LocalDate computeNextNotificationDate() {
@@ -310,7 +300,7 @@ public class ContiguousIntervalUsageInArrear {
                 if (prevRawUsage != null) {
                     if (prevRawUsage.getDate().compareTo(prevDate) >= 0 && prevRawUsage.getDate().compareTo(curDate) < 0) {
                         final Long currentAmount = perRangeUnitToAmount.get(prevRawUsage.getUnitType());
-                        final Long updatedAmount =  computeUpdatedAmount(currentAmount, prevRawUsage.getAmount());
+                        final Long updatedAmount = computeUpdatedAmount(currentAmount, prevRawUsage.getAmount());
                         perRangeUnitToAmount.put(prevRawUsage.getUnitType(), updatedAmount);
                         prevRawUsage = null;
                     }
@@ -331,7 +321,7 @@ public class ContiguousIntervalUsageInArrear {
                         }
 
                         final Long currentAmount = perRangeUnitToAmount.get(curRawUsage.getUnitType());
-                        final Long updatedAmount =  computeUpdatedAmount(currentAmount, curRawUsage.getAmount());
+                        final Long updatedAmount = computeUpdatedAmount(currentAmount, curRawUsage.getAmount());
                         perRangeUnitToAmount.put(curRawUsage.getUnitType(), updatedAmount);
                     }
                 }
@@ -369,133 +359,6 @@ public class ContiguousIntervalUsageInArrear {
         }
     }
 
-
-    private Limit getTierLimit(final Tier tier, final String unitType) {
-        for (final Limit cur : tier.getLimits()) {
-            if (cur.getUnit().getName().equals(unitType)) {
-                return cur;
-            }
-        }
-        Preconditions.checkState(false, "Could not find unit type " + unitType + " in usage tier ");
-        return null;
-    }
-
-    /**
-     * @param roUnits the list of rolled up units for the period
-     * @return the price amount that should be billed for that period/unitType
-     * @throws CatalogApiException
-     */
-    @VisibleForTesting
-    List<UsageInArrearDetail> computeToBeBilledCapacityInArrear(final List<RolledUpUnit> roUnits) throws CatalogApiException {
-        Preconditions.checkState(isBuilt.get());
-
-        final List<Tier> tiers = getCapacityInArrearTier(usage);
-        int tierNum = 0;
-        for (final Tier cur : tiers) {
-            tierNum++;
-            List<UsageInArrearDetail> toBeBilledDetails = Lists.newLinkedList();
-            boolean complies = true;
-            for (final RolledUpUnit ro : roUnits) {
-                final Limit tierLimit = getTierLimit(cur, ro.getUnitType());
-                // We ignore the min and only look at the max Limit as the tiers should be contiguous.
-                // Specifying a -1 value for last max tier will make the validation works
-                if (tierLimit.getMax() != (double) -1 && ro.getAmount().doubleValue() > tierLimit.getMax()) {
-                    complies = false;
-                    break;
-                }
-                toBeBilledDetails.add(new UsageInArrearDetail(tierNum, ro.getUnitType(), cur.getRecurringPrice().getPrice(getCurrency()), ro.getAmount().intValue(), BigDecimal.ZERO, BigDecimal.ZERO, ""));
-
-            }
-            if (complies) {
-                toBeBilledDetails.get(toBeBilledDetails.size() - 1).setAmount(cur.getRecurringPrice().getPrice(getCurrency()));
-                return toBeBilledDetails;
-            }
-        }
-        // Probably invalid catalog config
-        final Joiner joiner = Joiner.on(", ");
-        joiner.join(roUnits);
-        Preconditions.checkState(false, "Could not find tier for usage " + usage.getName()+ "matching with data = " + joiner.join(roUnits));
-        return null;
-    }
-
-    /**
-     * @param roUnit the rolled up unit for the period
-     * @return the price amount that should be billed for that period/unitType
-     * @throws CatalogApiException
-     */
-    @VisibleForTesting
-    List<UsageInArrearDetail> computeToBeBilledConsumableInArrear(final RolledUpUnit roUnit) throws CatalogApiException {
-
-        Preconditions.checkState(isBuilt.get());
-        final List<TieredBlock> tieredBlocks = getConsumableInArrearTieredBlocks(usage, roUnit.getUnitType());
-
-        switch (usage.getTierBlockPolicy()) {
-            case ALL_TIERS:
-                return computeToBeBilledConsumableInArrearWith_ALL_TIERS(tieredBlocks, roUnit.getAmount());
-            case TOP_TIER:
-                return Arrays.asList(computeToBeBilledConsumableInArrearWith_TOP_TIER(tieredBlocks, roUnit.getAmount()));
-            default:
-                throw new IllegalStateException("Unknown TierBlockPolicy " + usage.getTierBlockPolicy());
-        }
-    }
-
-
-    List<UsageInArrearDetail> computeToBeBilledConsumableInArrearWith_ALL_TIERS(final List<TieredBlock> tieredBlocks, final Long units) throws CatalogApiException {
-
-        List<UsageInArrearDetail> toBeBilledDetails = Lists.newLinkedList();
-        BigDecimal result = BigDecimal.ZERO;
-        int remainingUnits = units.intValue();
-        int tierNum = 0;
-        for (final TieredBlock tieredBlock : tieredBlocks) {
-
-            tierNum++;
-            final int blockTierSize = tieredBlock.getSize().intValue();
-            final int tmp = remainingUnits / blockTierSize + (remainingUnits % blockTierSize == 0 ? 0 : 1);
-            final int nbUsedTierBlocks;
-            if (tmp > tieredBlock.getMax()) {
-                nbUsedTierBlocks = tieredBlock.getMax().intValue();
-                remainingUnits -= tieredBlock.getMax() * blockTierSize;
-            } else {
-                nbUsedTierBlocks = tmp;
-                remainingUnits = 0;
-            }
-
-            if (nbUsedTierBlocks > 0) {
-                toBeBilledDetails.add(new UsageInArrearDetail(tierNum, tieredBlock.getUnit().getName(), tieredBlock.getPrice().getPrice(getCurrency()), nbUsedTierBlocks));
-            }
-        }
-        return toBeBilledDetails;
-    }
-
-    UsageInArrearDetail computeToBeBilledConsumableInArrearWith_TOP_TIER(final List<TieredBlock> tieredBlocks, final Long units) throws CatalogApiException {
-
-        int remainingUnits = units.intValue();
-
-        // By default last last tierBlock
-        TieredBlock targetBlock = tieredBlocks.get(tieredBlocks.size() - 1);
-        int targetTierNum = tieredBlocks.size();
-        int tierNum = 0;
-        // Loop through all tier block
-        for (final TieredBlock tieredBlock : tieredBlocks) {
-
-            tierNum++;
-            final int blockTierSize = tieredBlock.getSize().intValue();
-            final int tmp = remainingUnits / blockTierSize + (remainingUnits % blockTierSize == 0 ? 0 : 1);
-            if (tmp > tieredBlock.getMax()) {
-                remainingUnits -= tieredBlock.getMax() * blockTierSize;
-            } else {
-                targetBlock = tieredBlock;
-                targetTierNum = tierNum;
-                break;
-            }
-        }
-        final int lastBlockTierSize = targetBlock.getSize().intValue();
-        final int nbBlocks = units.intValue() / lastBlockTierSize + (units.intValue() % lastBlockTierSize == 0 ? 0 : 1);
-
-        return new UsageInArrearDetail(targetTierNum, targetBlock.getUnit().getName(), targetBlock.getPrice().getPrice(getCurrency()), nbBlocks);
-    }
-
-
     /**
      * @param filteredUsageForInterval the list of invoiceItem to consider
      * @return the price amount that was already billed for that period and usage section (across unitTypes)
@@ -512,10 +375,10 @@ public class ContiguousIntervalUsageInArrear {
         return billedAmount;
     }
 
-    Iterable<InvoiceItem> getBilledItems(final LocalDate startDate, final LocalDate endDate, final List<InvoiceItem> existingUsage) {
+    List<InvoiceItem> getBilledItems(final LocalDate startDate, final LocalDate endDate, final List<InvoiceItem> existingUsage) {
 
         Preconditions.checkState(isBuilt.get());
-        return Iterables.filter(existingUsage, new Predicate<InvoiceItem>() {
+        final Iterable<InvoiceItem> filteredResult = Iterables.filter(existingUsage, new Predicate<InvoiceItem>() {
             @Override
             public boolean apply(final InvoiceItem input) {
                 if (input.getInvoiceItemType() != InvoiceItemType.USAGE) {
@@ -528,6 +391,7 @@ public class ContiguousIntervalUsageInArrear {
                        usageInput.getEndDate().compareTo(endDate) <= 0;
             }
         });
+        return ImmutableList.copyOf(filteredResult);
     }
 
     @VisibleForTesting
@@ -599,155 +463,13 @@ public class ContiguousIntervalUsageInArrear {
         }
     }
 
-    public BigDecimal toBeBilledForUnit(List<UsageInArrearDetail> toBeBilledDetails){
-        BigDecimal result = BigDecimal.ZERO;
-        for (UsageInArrearDetail toBeBilled: toBeBilledDetails){
-            result = result.add(toBeBilled.getAmount());
+    protected String toJson(final UsageInArrearDetail usageInArrearDetail) {
+        try {
+            return objectMapper.writeValueAsString(usageInArrearDetail);
+        } catch (JsonProcessingException e) {
+            Preconditions.checkState(false, e.getMessage());
+            return null;
         }
-        return result;
     }
 
-    private List<UsageInArrearDetail> reconcileExistedBilledWithToBeBilled(Iterable<InvoiceItem> billedItems, List<UsageInArrearDetail> toBeBilledUsageInArrearDetails) {
-        for (final InvoiceItem bi : billedItems) {
-            List<UsageInArrearDetail> billedUsageItemDetails = fromJson(bi.getItemDetails());
-
-            if (billedUsageItemDetails != null && billedUsageItemDetails.size() > 0) {
-
-                for (final UsageInArrearDetail toBeBilledConsumable : toBeBilledUsageInArrearDetails) {
-                    billedUsageItemDetails = toBeBilledConsumable.reconcile(billedUsageItemDetails);
-                }
-
-                if (billedUsageItemDetails != null && billedUsageItemDetails.size() > 0) {
-                    for (final UsageInArrearDetail billedUsage : billedUsageItemDetails) {
-                        toBeBilledUsageInArrearDetails.add(new UsageInArrearDetail(billedUsage.getTier(), billedUsage.getTierUnit(), billedUsage.getTierPrice(),
-                                                                                   billedUsage.getQuantity() * -1, billedUsage.getAmount().negate(), null, bi.getId().toString()));
-                    }
-                }
-            } else {
-                toBeBilledUsageInArrearDetails.get(0).setAmount(toBeBilledUsageInArrearDetails.get(0).getAmount().subtract(bi.getAmount()));
-                toBeBilledUsageInArrearDetails.get(0).setQuantity(toBeBilledUsageInArrearDetails.get(0).getQuantity() - (bi.getQuantity() == null ? 0 : bi.getQuantity()));
-                toBeBilledUsageInArrearDetails.get(0).setExistingUsageAmount(toBeBilledUsageInArrearDetails.get(0).getExistingUsageAmount().add(bi.getAmount()));
-                toBeBilledUsageInArrearDetails.get(0).setReference(toBeBilledUsageInArrearDetails.get(0).getReference().concat(":").concat(bi.getId().toString()));
-            }
-        }
-
-        return toBeBilledUsageInArrearDetails;
-    }
-
-    private static final String toJson(List<UsageInArrearDetail> toBeBilledUsageInArrearDetails) {
-        String result = null;
-        if (toBeBilledUsageInArrearDetails != null && toBeBilledUsageInArrearDetails.size() > 0){
-            try {
-                result = objectMapper.writeValueAsString(toBeBilledUsageInArrearDetails);
-            } catch (JsonProcessingException e) {
-                Preconditions.checkState(false, e.getMessage());
-            }
-        }
-        return result;
-    }
-
-    private static final List<UsageInArrearDetail> fromJson(String itemDetails){
-        List<UsageInArrearDetail> toBeBilledUsageInArrearDetails = null;
-        if (itemDetails != null){
-            try {
-                toBeBilledUsageInArrearDetails = objectMapper.readValue(itemDetails, new TypeReference<List<UsageInArrearDetail>>() {});
-            } catch (IOException e) {
-                Preconditions.checkState(false, e.getMessage());
-            }
-        }
-
-        return toBeBilledUsageInArrearDetails;
-    }
-
-    public static class UsageInArrearDetail {
-
-        private final int tier;
-        private final String tierUnit;
-        private final BigDecimal tierPrice;
-        private Integer quantity;
-        private String reference;
-        private BigDecimal existingUsageAmount;
-        private BigDecimal amount;
-
-        public UsageInArrearDetail(BigDecimal tierPrice, Integer quantity, BigDecimal existingUsageAmount, String reference) {
-            this(0, null, tierPrice, quantity, existingUsageAmount, BigDecimal.ZERO, reference);
-        }
-
-        public UsageInArrearDetail(int tier, String tierUnit, BigDecimal tierPrice, Integer quantity) {
-            this(tier, tierUnit, tierPrice, quantity, tierPrice.multiply(new BigDecimal(quantity)), BigDecimal.ZERO, "");
-        }
-
-        @JsonCreator
-        public UsageInArrearDetail(@JsonProperty("tier") int tier, @JsonProperty("tierUnit") String tierUnit,
-                                   @JsonProperty("tierPrice") BigDecimal tierPrice, @JsonProperty("quantity") Integer quantity,
-                                   @JsonProperty("amount") BigDecimal amount, @JsonProperty("existingUsageAmount") BigDecimal existingUsageAmount,
-                                   @JsonProperty("reference") String reference) {
-            this.tier = tier;
-            this.tierUnit = tierUnit;
-            this.tierPrice = tierPrice;
-            this.quantity = quantity;
-            this.amount = amount;
-            this.existingUsageAmount = existingUsageAmount;
-            this.reference = reference;
-        }
-
-        public int getTier() {
-            return tier;
-        }
-
-        public String getTierUnit() {
-            return tierUnit;
-        }
-
-        public BigDecimal getTierPrice() {
-            return tierPrice;
-        }
-
-        public Integer getQuantity() {
-            return quantity;
-        }
-
-        public BigDecimal getExistingUsageAmount() {
-            return existingUsageAmount;
-        }
-
-        public String getReference() {
-            return reference;
-        }
-
-        public void setExistingUsageAmount(BigDecimal existingUsageAmount) {
-            this.existingUsageAmount = existingUsageAmount;
-        }
-
-        public BigDecimal getAmount() {
-            return amount;
-        }
-
-        public void setQuantity(Integer quantity) {
-            this.quantity = quantity;
-        }
-
-        public void setAmount(BigDecimal amount) {
-            this.amount = amount;
-        }
-
-        public void setReference(String reference) {
-            this.reference = reference;
-        }
-
-        public List<UsageInArrearDetail> reconcile(List<UsageInArrearDetail> billedUsageItemDetails) {
-            List<UsageInArrearDetail> unreconciledUsage = Lists.newLinkedList();
-            for (UsageInArrearDetail billedUsageDetail : billedUsageItemDetails) {
-                if (tierUnit.equals(billedUsageDetail.tierUnit)) {
-                    existingUsageAmount = billedUsageDetail.getAmount().abs();
-                    quantity = quantity - billedUsageDetail.getQuantity();
-                    amount = amount.subtract(existingUsageAmount);
-                } else {
-                    unreconciledUsage.add(billedUsageDetail);
-                }
-            }
-
-            return unreconciledUsage;
-        }
-    }
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageCapacityInArrearDetail.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageCapacityInArrearDetail.java
new file mode 100644
index 0000000..4baa4d9
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageCapacityInArrearDetail.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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.invoice.usage.details;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class UsageCapacityInArrearDetail implements UsageInArrearDetail {
+
+    private final List<UsageInArrearTierUnitDetail> tierDetails;
+    private BigDecimal amount;
+
+    @JsonCreator
+    public UsageCapacityInArrearDetail(@JsonProperty("tierDetails") List<UsageInArrearTierUnitDetail> tierDetails,
+                                       @JsonProperty("amount") BigDecimal amount) {
+        this.tierDetails = tierDetails;
+        this.amount = amount;
+    }
+
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    public void setAmount(BigDecimal amount) {
+        this.amount = amount;
+    }
+
+    public List<UsageInArrearTierUnitDetail> getTierDetails() {
+        return tierDetails;
+    }
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageConsumableInArrearDetail.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageConsumableInArrearDetail.java
new file mode 100644
index 0000000..8d690bb
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageConsumableInArrearDetail.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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.invoice.usage.details;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class UsageConsumableInArrearDetail implements UsageInArrearDetail {
+
+    private final List<UsageConsumableInArrearTierUnitDetail> tierDetails;
+    private BigDecimal amount;
+
+    public UsageConsumableInArrearDetail(@JsonProperty("tierDetails") List<UsageConsumableInArrearTierUnitDetail> tierDetails) {
+        this(tierDetails, computeAmount(tierDetails));
+    }
+
+    @JsonCreator
+    public UsageConsumableInArrearDetail(@JsonProperty("tierDetails") List<UsageConsumableInArrearTierUnitDetail> tierDetails,
+                                         @JsonProperty("amount") BigDecimal amount) {
+        this.tierDetails = tierDetails;
+        this.amount = amount;
+    }
+
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    public List<UsageConsumableInArrearTierUnitDetail> getTierDetails() {
+        return tierDetails;
+    }
+
+    private static BigDecimal computeAmount(final List<UsageConsumableInArrearTierUnitDetail> tierDetails) {
+        BigDecimal result = BigDecimal.ZERO;
+        for (UsageConsumableInArrearTierUnitDetail toBeBilled : tierDetails) {
+            result = result.add(toBeBilled.getAmount());
+        }
+        return result;
+    }
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageConsumableInArrearTierUnitDetail.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageConsumableInArrearTierUnitDetail.java
new file mode 100644
index 0000000..de7b59b
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageConsumableInArrearTierUnitDetail.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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.invoice.usage.details;
+
+import java.math.BigDecimal;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class UsageConsumableInArrearTierUnitDetail extends UsageInArrearTierUnitDetail {
+
+    private final int tierBlockSize;
+    private BigDecimal amount;
+
+    public UsageConsumableInArrearTierUnitDetail(int tier, String tierUnit, BigDecimal tierPrice, Integer tierBlockSize, Integer quantity) {
+        this(tier, tierUnit, tierPrice, tierBlockSize, quantity, tierPrice.multiply(new BigDecimal(quantity)));
+    }
+
+    @JsonCreator
+    public UsageConsumableInArrearTierUnitDetail(@JsonProperty("tier") int tier,
+                                                 @JsonProperty("tierUnit") String tierUnit,
+                                                 @JsonProperty("tierPrice") BigDecimal tierPrice,
+                                                 @JsonProperty("tierBlockSize") Integer tierBlockSize,
+                                                 @JsonProperty("quantity") Integer quantity,
+                                                 @JsonProperty("amount") BigDecimal amount) {
+        super(tier, tierUnit, tierPrice, quantity);
+        this.amount = amount;
+        this.tierBlockSize = tierBlockSize;
+    }
+
+    public int getTier() {
+        return tier;
+    }
+
+    public String getTierUnit() {
+        return tierUnit;
+    }
+
+    public BigDecimal getTierPrice() {
+        return tierPrice;
+    }
+
+    public Integer getQuantity() {
+        return quantity;
+    }
+
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    public void setAmount(BigDecimal amount) {
+        this.amount = amount;
+    }
+
+    public int getTierBlockSize() {
+        return tierBlockSize;
+    }
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageInArrearDetail.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageInArrearDetail.java
new file mode 100644
index 0000000..c04ed95
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageInArrearDetail.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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.invoice.usage.details;
+
+import java.math.BigDecimal;
+
+public interface UsageInArrearDetail {
+
+    BigDecimal getAmount();
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageInArrearTierUnitDetail.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageInArrearTierUnitDetail.java
new file mode 100644
index 0000000..59aebc7
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageInArrearTierUnitDetail.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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.invoice.usage.details;
+
+import java.math.BigDecimal;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class UsageInArrearTierUnitDetail {
+
+    protected final int tier;
+    protected final String tierUnit;
+    protected final BigDecimal tierPrice;
+    protected Integer quantity;
+
+    @JsonCreator
+    public UsageInArrearTierUnitDetail(@JsonProperty("tier") int tier,
+                                       @JsonProperty("tierUnit") String tierUnit,
+                                       @JsonProperty("tierPrice") BigDecimal tierPrice,
+                                       @JsonProperty("quantity") Integer quantity) {
+        this.tier = tier;
+        this.tierUnit = tierUnit;
+        this.tierPrice = tierPrice;
+        this.quantity = quantity;
+    }
+
+    public int getTier() {
+        return tier;
+    }
+
+    public String getTierUnit() {
+        return tierUnit;
+    }
+
+    public BigDecimal getTierPrice() {
+        return tierPrice;
+    }
+
+    public Integer getQuantity() {
+        return quantity;
+    }
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionUsageInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionUsageInArrear.java
index b104127..93bbd42 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionUsageInArrear.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionUsageInArrear.java
@@ -33,6 +33,7 @@ import org.killbill.billing.catalog.api.BillingMode;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.Usage;
 import org.killbill.billing.catalog.api.UsageType;
+import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.generator.InvoiceItemGenerator.InvoiceItemGeneratorLogger;
 import org.killbill.billing.invoice.usage.ContiguousIntervalUsageInArrear.UsageInArrearItemsAndNextNotificationDate;
@@ -113,7 +114,7 @@ public class SubscriptionUsageInArrear {
      * @param existingUsage the existing on disk usage items.
      * @throws CatalogApiException
      */
-    public SubscriptionUsageInArrearItemsAndNextNotificationDate computeMissingUsageInvoiceItems(final List<InvoiceItem> existingUsage, final InvoiceItemGeneratorLogger invoiceItemGeneratorLogger) throws CatalogApiException {
+    public SubscriptionUsageInArrearItemsAndNextNotificationDate computeMissingUsageInvoiceItems(final List<InvoiceItem> existingUsage, final InvoiceItemGeneratorLogger invoiceItemGeneratorLogger) throws CatalogApiException, InvoiceApiException {
         final SubscriptionUsageInArrearItemsAndNextNotificationDate result = new SubscriptionUsageInArrearItemsAndNextNotificationDate();
         final List<ContiguousIntervalUsageInArrear> billingEventTransitionTimePeriods = computeInArrearUsageInterval();
         for (final ContiguousIntervalUsageInArrear usageInterval : billingEventTransitionTimePeriods) {
@@ -154,7 +155,10 @@ public class SubscriptionUsageInArrear {
                 // Add inflight usage interval if non existent
                 ContiguousIntervalUsageInArrear existingInterval = inFlightInArrearUsageIntervals.get(usage.getName());
                 if (existingInterval == null) {
-                    existingInterval = new ContiguousIntervalUsageInArrear(usage, accountId, invoiceId, rawSubscriptionUsage, targetDate, rawUsageStartDate, usageDetailMode, internalTenantContext);
+                    existingInterval = usage.getUsageType() == UsageType.CAPACITY ?
+                                       new ContiguousIntervalCapacityUsageInArrear(usage, accountId, invoiceId, rawSubscriptionUsage, targetDate, rawUsageStartDate, usageDetailMode, internalTenantContext) :
+                                       new ContiguousIntervalConsumableUsageInArrear(usage, accountId, invoiceId, rawSubscriptionUsage, targetDate, rawUsageStartDate, usageDetailMode, internalTenantContext);
+
                     inFlightInArrearUsageIntervals.put(usage.getName(), existingInterval);
                 }
                 // Add billing event for that usage interval
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/UsageUtils.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/UsageUtils.java
index 69baa60..5e0e026 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/UsageUtils.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/UsageUtils.java
@@ -16,10 +16,8 @@
 
 package org.killbill.billing.invoice.usage;
 
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 import org.killbill.billing.catalog.api.BillingMode;
@@ -40,13 +38,19 @@ public class UsageUtils {
         Preconditions.checkArgument(usage.getBillingMode() == BillingMode.IN_ARREAR && usage.getUsageType() == UsageType.CONSUMABLE);
         Preconditions.checkArgument(usage.getTiers().length > 0);
 
+
         final List<TieredBlock> result = Lists.newLinkedList();
         for (Tier tier : usage.getTiers()) {
+            boolean found = false;
             for (TieredBlock tierBlock : tier.getTieredBlocks()) {
                 if (tierBlock.getUnit().getName().equals(unitType)) {
                     result.add(tierBlock);
+                    found = true;
+                    break;
                 }
             }
+            // We expect this method to return an ordered list of TieredBlock, each for each tier.
+            Preconditions.checkState(found, "Catalog issue in usage section '%s': Missing tierBlock definition for unit '%s'", usage.getName(), unitType);
         }
         return result;
     }
@@ -65,7 +69,6 @@ public class UsageUtils {
         return result;
     }
 
-
     public static List<Tier> getCapacityInArrearTier(final Usage usage) {
 
         Preconditions.checkArgument(usage.getBillingMode() == BillingMode.IN_ARREAR && usage.getUsageType() == UsageType.CAPACITY);
@@ -73,7 +76,6 @@ public class UsageUtils {
         return ImmutableList.copyOf(usage.getTiers());
     }
 
-
     public static Set<String> getCapacityInArrearUnitTypes(final Usage usage) {
 
         Preconditions.checkArgument(usage.getBillingMode() == BillingMode.IN_ARREAR && usage.getUsageType() == UsageType.CAPACITY);
@@ -88,5 +90,4 @@ public class UsageUtils {
         return result;
     }
 
-
 }
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalCapacityInArrear.java b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalCapacityInArrear.java
index 14cbf50..8ffd615 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalCapacityInArrear.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalCapacityInArrear.java
@@ -27,19 +27,19 @@ import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
 import org.killbill.billing.catalog.DefaultLimit;
 import org.killbill.billing.catalog.DefaultTier;
-import org.killbill.billing.catalog.DefaultTieredBlock;
 import org.killbill.billing.catalog.DefaultUnit;
 import org.killbill.billing.catalog.DefaultUsage;
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.Currency;
-import org.killbill.billing.catalog.api.TierBlockPolicy;
 import org.killbill.billing.catalog.api.Usage;
+import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.model.FixedPriceInvoiceItem;
 import org.killbill.billing.invoice.model.UsageInvoiceItem;
-import org.killbill.billing.invoice.usage.ContiguousIntervalUsageInArrear.UsageInArrearDetail;
 import org.killbill.billing.invoice.usage.ContiguousIntervalUsageInArrear.UsageInArrearItemsAndNextNotificationDate;
+import org.killbill.billing.invoice.usage.details.UsageCapacityInArrearDetail;
+import org.killbill.billing.invoice.usage.details.UsageInArrearTierUnitDetail;
 import org.killbill.billing.junction.BillingEvent;
 import org.killbill.billing.usage.RawUsage;
 import org.killbill.billing.usage.api.RolledUpUnit;
@@ -85,11 +85,11 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
 
         final LocalDate targetDate = startDate.plusDays(1);
 
-        final ContiguousIntervalUsageInArrear intervalCapacityInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
-                                                                                                                      createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
-                                                                                                                                             BillingPeriod.MONTHLY,
-                                                                                                                                             Collections.<Usage>emptyList())
-                                                                                                                     );
+        final ContiguousIntervalUsageInArrear intervalCapacityInArrear = createContiguousIntervalCapacityInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
+                                                                                                                  createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
+                                                                                                                                         BillingPeriod.MONTHLY,
+                                                                                                                                         Collections.<Usage>emptyList())
+                                                                                                                 );
 
         final List<InvoiceItem> existingUsage = Lists.newArrayList();
         final UsageInvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usage.getName(), startDate, endDate, BigDecimal.TEN, currency);
@@ -117,7 +117,6 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
     @Test(groups = "fast")
     public void testComputeBilledUsage() throws CatalogApiException {
 
-
         final DefaultUnit unit1 = new DefaultUnit().setName("unit1");
         final DefaultUnit unit2 = new DefaultUnit().setName("unit2");
         final DefaultUnit unit3 = new DefaultUnit().setName("unit3");
@@ -138,73 +137,83 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
         final DefaultLimit limit3_3 = new DefaultLimit().setUnit(unit3).setMax((double) -1).setMin((double) -1);
         final DefaultTier tier3 = createDefaultTierWithLimits(new BigDecimal("30.0"), limit3_1, limit3_2, limit3_3);
 
-
         final DefaultUsage usage = createCapacityInArrearUsage(usageName, BillingPeriod.MONTHLY, tier1, tier2, tier3);
 
         final LocalDate targetDate = new LocalDate(2014, 03, 20);
 
-        final ContiguousIntervalUsageInArrear intervalCapacityInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
-                                                                                                                      createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
-                                                                                                                                                  BillingPeriod.MONTHLY,
-                                                                                                                                                  Collections.<Usage>emptyList())
-                                                                                                                     );
+        final ContiguousIntervalCapacityUsageInArrear intervalCapacityInArrear = createContiguousIntervalCapacityInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
+                                                                                                                  createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
+                                                                                                                                         BillingPeriod.MONTHLY,
+                                                                                                                                         Collections.<Usage>emptyList())
+                                                                                                                 );
         // Tier 1 (both units from tier 1)
-        List<UsageInArrearDetail> result = intervalCapacityInArrear.computeToBeBilledCapacityInArrear(ImmutableList.<RolledUpUnit>of(new DefaultRolledUpUnit("unit1", 100L),
-                                                                                                                                     new DefaultRolledUpUnit("unit2", 1000L),
-                                                                                                                                     new DefaultRolledUpUnit("unit3", 50L)));
-        assertEquals(result.size(), 3);
-        assertTrue(intervalCapacityInArrear.toBeBilledForUnit(result).compareTo(BigDecimal.TEN) == 0);
+        UsageCapacityInArrearDetail result = intervalCapacityInArrear.computeToBeBilledCapacityInArrear(ImmutableList.<RolledUpUnit>of(new DefaultRolledUpUnit("unit1", 100L),
+                                                                                                                                       new DefaultRolledUpUnit("unit2", 1000L),
+                                                                                                                                       new DefaultRolledUpUnit("unit3", 50L)));
+        assertEquals(result.getTierDetails().size(), 3);
+        assertTrue(result.getAmount().compareTo(BigDecimal.TEN) == 0);
 
         // Tier 2 (only one unit from tier 1)
         result = intervalCapacityInArrear.computeToBeBilledCapacityInArrear(ImmutableList.<RolledUpUnit>of(new DefaultRolledUpUnit("unit1", 100L),
                                                                                                            new DefaultRolledUpUnit("unit2", 1001L)));
-        assertTrue(intervalCapacityInArrear.toBeBilledForUnit(result).compareTo(new BigDecimal("20.0")) == 0);
+        assertTrue(result.getAmount().compareTo(new BigDecimal("20.0")) == 0);
 
         // Tier 2 (only one unit from tier 1)
         result = intervalCapacityInArrear.computeToBeBilledCapacityInArrear(ImmutableList.<RolledUpUnit>of(new DefaultRolledUpUnit("unit1", 101L),
                                                                                                            new DefaultRolledUpUnit("unit2", 1000L)));
-        assertTrue(intervalCapacityInArrear.toBeBilledForUnit(result).compareTo(new BigDecimal("20.0")) == 0);
-
+        assertTrue(result.getAmount().compareTo(new BigDecimal("20.0")) == 0);
 
         // Tier 2 (both units from tier 2)
         result = intervalCapacityInArrear.computeToBeBilledCapacityInArrear(ImmutableList.<RolledUpUnit>of(new DefaultRolledUpUnit("unit1", 101L),
                                                                                                            new DefaultRolledUpUnit("unit2", 1001L)));
-        assertTrue(intervalCapacityInArrear.toBeBilledForUnit(result).compareTo(new BigDecimal("20.0")) == 0);
+        assertTrue(result.getAmount().compareTo(new BigDecimal("20.0")) == 0);
 
         // Tier 3 (only one unit from tier 3)
         result = intervalCapacityInArrear.computeToBeBilledCapacityInArrear(ImmutableList.<RolledUpUnit>of(new DefaultRolledUpUnit("unit1", 10L),
                                                                                                            new DefaultRolledUpUnit("unit2", 2001L)));
-        assertTrue(intervalCapacityInArrear.toBeBilledForUnit(result).compareTo(new BigDecimal("30.0")) == 0);
+        assertTrue(result.getAmount().compareTo(new BigDecimal("30.0")) == 0);
     }
 
     @Test(groups = "fast")
-    public void testComputeMissingItems() throws CatalogApiException {
+    public void testComputeMissingItems() throws CatalogApiException, InvoiceApiException {
 
         final LocalDate startDate = new LocalDate(2014, 03, 20);
         final LocalDate firstBCDDate = new LocalDate(2014, 04, 15);
         final LocalDate endDate = new LocalDate(2014, 05, 15);
 
-        // 2 items for startDate - firstBCDDate
         final List<RawUsage> rawUsages = new ArrayList<RawUsage>();
-        rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "unit", 130L));
-        rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "unit", 271L));
-        // 1 items for firstBCDDate - endDate
-        rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 04, 15), "unit", 199L));
+        //
+        // First period: startDate - firstBCDDate
+        //
+        // 2 items for unit1
+        rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "unit1", 130L));
+        rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "unit1", 271L));
+        // 1 items for unit2
+        rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 24), "unit2", 10L));
+
+        //
+        // Second period: firstBCDDate - endDate
+        //
+        // 1 items unit1
+        rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 04, 15), "unit1", 199L));
+        // 1 items unit2
+        rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 04, 15), "unit2", 20L));
 
-        final DefaultUnit unit = new DefaultUnit().setName("unit");
-        final DefaultLimit limit = new DefaultLimit().setUnit(unit).setMax((double) -1);
+        final DefaultUnit unit1 = new DefaultUnit().setName("unit1");
+        final DefaultLimit limit1 = new DefaultLimit().setUnit(unit1).setMax((double) -1);
 
-        final DefaultTier tier = createDefaultTierWithLimits(BigDecimal.TEN, limit);
+        final DefaultUnit unit2 = new DefaultUnit().setName("unit2");
+        final DefaultLimit limit2 = new DefaultLimit().setUnit(unit2).setMax((double) -1);
 
-        final DefaultUsage usage = createCapacityInArrearUsage(usageName, BillingPeriod.MONTHLY, tier);
+        final DefaultTier tier = createDefaultTierWithLimits(BigDecimal.TEN, limit1, limit2);
 
+        final DefaultUsage usage = createCapacityInArrearUsage(usageName, BillingPeriod.MONTHLY, tier);
 
         final LocalDate targetDate = endDate;
-
-        final BillingEvent event1 = createMockBillingEvent(startDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
+        final BillingEvent event1 = createMockBillingEvent(startDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
         final BillingEvent event2 = createMockBillingEvent(endDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
 
-        final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, rawUsages, targetDate, true, event1, event2);
+        final ContiguousIntervalCapacityUsageInArrear intervalCapacityInArrear = createContiguousIntervalCapacityInArrear(usage, rawUsages, targetDate, true, event1, event2);
 
         final List<InvoiceItem> invoiceItems = new ArrayList<InvoiceItem>();
         final InvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usage.getName(), startDate, firstBCDDate, BigDecimal.ONE, currency);
@@ -213,7 +222,7 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
         final InvoiceItem ii2 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usage.getName(), firstBCDDate, endDate, BigDecimal.ONE, currency);
         invoiceItems.add(ii2);
 
-        final UsageInArrearItemsAndNextNotificationDate usageResult = intervalConsumableInArrear.computeMissingItemsAndNextNotificationDate(invoiceItems);
+        final UsageInArrearItemsAndNextNotificationDate usageResult = intervalCapacityInArrear.computeMissingItemsAndNextNotificationDate(invoiceItems);
         final List<InvoiceItem> rawResults = usageResult.getInvoiceItems();
         assertEquals(rawResults.size(), 4);
 
@@ -224,7 +233,6 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
             }
         }));
 
-
         assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("9.0")), 0, String.format("%s != 9.0", result.get(0).getAmount()));
         assertEquals(result.get(0).getCurrency(), Currency.BTC);
         assertEquals(result.get(0).getAccountId(), accountId);
@@ -249,82 +257,137 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
     }
 
     @Test(groups = "fast")
-    public void testComputeMissingItemsAggregateMode() throws CatalogApiException, IOException {
-
-        testComputeMissingItemsByMode(UsageDetailMode.AGGREGATE);
+    public void testMultipleItemsAndTiersAggregateMode() throws CatalogApiException, IOException, InvoiceApiException {
+        testMultipleItemsAndTiers(UsageDetailMode.AGGREGATE);
     }
 
     @Test(groups = "fast")
-    public void testComputeMissingItemsDetailMode() throws CatalogApiException, IOException {
-
-       testComputeMissingItemsByMode(UsageDetailMode.DETAIL);
+    public void testMultipleItemsAndTiersDetailMode() throws CatalogApiException, IOException, InvoiceApiException {
+        testMultipleItemsAndTiers(UsageDetailMode.DETAIL);
     }
 
-    private void testComputeMissingItemsByMode(UsageDetailMode usageDetailMode) throws CatalogApiException, IOException {
+    private void testMultipleItemsAndTiers(UsageDetailMode usageDetailMode) throws CatalogApiException, IOException, InvoiceApiException {
 
         // Case 1
         List<RawUsage> rawUsages = new ArrayList<RawUsage>();
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 99L));
 
-        List<InvoiceItem> result = produceInvoiceItems(rawUsages, usageDetailMode);
+        List<InvoiceItem> result = produceInvoiceItems(rawUsages, usageDetailMode, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 1);
-        assertEquals(result.get(0).getAmount().compareTo(BigDecimal.ONE),0, String.format("%s != 1.0", result.get(0).getAmount()));
+        assertEquals(result.get(0).getAmount().compareTo(BigDecimal.ONE), 0, String.format("%s != 1.0", result.get(0).getAmount()));
 
-        List<UsageInArrearDetail> itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
+        UsageCapacityInArrearDetail itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageCapacityInArrearDetail>() {});
+        assertEquals(itemDetails.getAmount().compareTo(BigDecimal.ONE), 0);
+        assertEquals(itemDetails.getTierDetails().size(), 2);
+
+        List<UsageInArrearTierUnitDetail> itemUnitDetails = itemDetails.getTierDetails();
         // BAR item detail
-        assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
-        assertEquals(itemDetails.get(0).getTier(), 1);
-        assertEquals(itemDetails.get(0).getQuantity().intValue(), 99);
-        assertEquals(itemDetails.get(0).getTierPrice().compareTo(BigDecimal.ONE), 0);
+        assertEquals(itemUnitDetails.get(0).getTierUnit(), "BAR");
+        assertEquals(itemUnitDetails.get(0).getTier(), 1);
+        assertEquals(itemUnitDetails.get(0).getQuantity().intValue(), 99);
+        assertEquals(itemUnitDetails.get(0).getTierPrice().compareTo(BigDecimal.ONE), 0);
         // FOO item detail
-        assertEquals(itemDetails.get(1).getTierUnit(), "FOO");
-        assertEquals(itemDetails.get(1).getTier(), 1);
-        assertEquals(itemDetails.get(1).getQuantity().intValue(), 5);
-        assertEquals(itemDetails.get(1).getTierPrice().compareTo(BigDecimal.ONE), 0);
+        assertEquals(itemUnitDetails.get(1).getTierUnit(), "FOO");
+        assertEquals(itemUnitDetails.get(1).getTier(), 1);
+        assertEquals(itemUnitDetails.get(1).getQuantity().intValue(), 5);
+        assertEquals(itemUnitDetails.get(1).getTierPrice().compareTo(BigDecimal.ONE), 0);
 
         // Case 2
         rawUsages = new ArrayList<RawUsage>();
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
-        result = produceInvoiceItems(rawUsages, usageDetailMode);
+        result = produceInvoiceItems(rawUsages, usageDetailMode, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 1);
-        assertEquals(result.get(0).getAmount().compareTo(BigDecimal.TEN),0, String.format("%s != 10.0", result.get(0).getAmount()));
+        assertEquals(result.get(0).getAmount().compareTo(BigDecimal.TEN), 0, String.format("%s != 10.0", result.get(0).getAmount()));
+
+        itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageCapacityInArrearDetail>() {});
+        assertEquals(itemDetails.getAmount().compareTo(BigDecimal.TEN), 0);
+        assertEquals(itemDetails.getTierDetails().size(), 2);
+        itemUnitDetails = itemDetails.getTierDetails();
 
-        itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
-        // BAR item detail
-        assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
-        assertEquals(itemDetails.get(0).getTier(), 2);
-        assertEquals(itemDetails.get(0).getQuantity().intValue(), 101);
-        assertEquals(itemDetails.get(0).getTierPrice().compareTo(BigDecimal.TEN), 0);
         // FOO item detail
-        assertEquals(itemDetails.get(1).getTierUnit(), "FOO");
-        assertEquals(itemDetails.get(1).getTier(), 2);
-        assertEquals(itemDetails.get(1).getQuantity().intValue(), 5);
-        assertEquals(itemDetails.get(1).getTierPrice().compareTo(BigDecimal.TEN), 0);
+        assertEquals(itemUnitDetails.get(0).getTierUnit(), "FOO");
+        assertEquals(itemUnitDetails.get(0).getTier(), 1);
+        assertEquals(itemUnitDetails.get(0).getQuantity().intValue(), 5);
+        assertEquals(itemUnitDetails.get(0).getTierPrice().compareTo(BigDecimal.ONE), 0);
+
+        // BAR item detail
+        assertEquals(itemUnitDetails.get(1).getTierUnit(), "BAR");
+        assertEquals(itemUnitDetails.get(1).getTier(), 2);
+        assertEquals(itemUnitDetails.get(1).getQuantity().intValue(), 101);
+        assertEquals(itemUnitDetails.get(1).getTierPrice().compareTo(BigDecimal.TEN), 0);
 
         // Case 3
         rawUsages = new ArrayList<RawUsage>();
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 75L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
-        result = produceInvoiceItems(rawUsages, usageDetailMode);
+        result = produceInvoiceItems(rawUsages, usageDetailMode, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 1);
-        assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("100.0")),0, String.format("%s != 100.0", result.get(0).getAmount()));
+        assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("100.0")), 0, String.format("%s != 100.0", result.get(0).getAmount()));
+
+        itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageCapacityInArrearDetail>() {});
+        assertEquals(itemDetails.getAmount().compareTo(new BigDecimal("100.0")), 0);
+        assertEquals(itemDetails.getTierDetails().size(), 2);
+        itemUnitDetails = itemDetails.getTierDetails();
 
-        itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
         // BAR item detail
-        assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
-        assertEquals(itemDetails.get(0).getTier(), 3);
-        assertEquals(itemDetails.get(0).getQuantity().intValue(), 101);
-        assertEquals(itemDetails.get(0).getTierPrice().compareTo(new BigDecimal("100.0")), 0);
+        assertEquals(itemUnitDetails.get(0).getTierUnit(), "BAR");
+        assertEquals(itemUnitDetails.get(0).getTier(), 2);
+        assertEquals(itemUnitDetails.get(0).getQuantity().intValue(), 101);
+        assertEquals(itemUnitDetails.get(0).getTierPrice().compareTo(new BigDecimal("10.0")), 0);
+
         // FOO item detail
-        assertEquals(itemDetails.get(1).getTierUnit(), "FOO");
-        assertEquals(itemDetails.get(1).getTier(), 3);
-        assertEquals(itemDetails.get(1).getQuantity().intValue(), 75);
-        assertEquals(itemDetails.get(1).getTierPrice().compareTo(new BigDecimal("100.0")), 0);
+        assertEquals(itemUnitDetails.get(1).getTierUnit(), "FOO");
+        assertEquals(itemUnitDetails.get(1).getTier(), 3);
+        assertEquals(itemUnitDetails.get(1).getQuantity().intValue(), 75);
+        assertEquals(itemUnitDetails.get(1).getTierPrice().compareTo(new BigDecimal("100.0")), 0);
+
     }
 
-    private List<InvoiceItem> produceInvoiceItems(List<RawUsage> rawUsages, UsageDetailMode usageDetailMode) throws CatalogApiException {
+    @Test(groups = "fast")
+    public void testMultipleItemsAndTiersWithExistingItems() throws CatalogApiException, IOException, InvoiceApiException {
+
+        // let's assume we have some existing usage
+        final UsageInArrearTierUnitDetail existingFooUsageTier1 = new UsageInArrearTierUnitDetail(1, "FOO", BigDecimal.ONE, 9);
+        final UsageInArrearTierUnitDetail existingBarUsageTier2 = new UsageInArrearTierUnitDetail(2, "BAR", BigDecimal.TEN, 200);
+
+
+        List<RawUsage> rawUsages = new ArrayList<RawUsage>();
+        rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 60L)); // tier 3
+        rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 200L)); // tier 2
+
+        final List<UsageInArrearTierUnitDetail> existingUsage = ImmutableList.of(existingFooUsageTier1, existingBarUsageTier2);
+
+        final String existingUsageJson = objectMapper.writeValueAsString(new UsageCapacityInArrearDetail(existingUsage, BigDecimal.TEN));
+
+        final List<InvoiceItem> existingItems = new ArrayList<InvoiceItem>();
+        final InvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, new LocalDate(2014, 03, 20), new LocalDate(2014, 04, 15), BigDecimal.TEN, null, currency, null, existingUsageJson);
+        existingItems.add(ii1);
+
+        List<InvoiceItem> result = produceInvoiceItems(rawUsages, UsageDetailMode.AGGREGATE, existingItems);
+        assertEquals(result.size(), 1);
+        assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("90.00")), 0, String.format("%s != 90.0", result.get(0).getAmount()));
+
+        UsageCapacityInArrearDetail itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageCapacityInArrearDetail>() {});
+        assertEquals(itemDetails.getAmount().compareTo(new BigDecimal("100.00")), 0);
+        assertEquals(itemDetails.getTierDetails().size(), 2);
+
+        List<UsageInArrearTierUnitDetail> itemUnitDetails = itemDetails.getTierDetails();
+
+        // BAR item detail
+        assertEquals(itemUnitDetails.get(0).getTierUnit(), "BAR");
+        assertEquals(itemUnitDetails.get(0).getTier(), 2);
+        assertEquals(itemUnitDetails.get(0).getQuantity().intValue(), 200);
+        assertEquals(itemUnitDetails.get(0).getTierPrice().compareTo(BigDecimal.TEN), 0);
+        // FOO item detail
+        assertEquals(itemUnitDetails.get(1).getTierUnit(), "FOO");
+        assertEquals(itemUnitDetails.get(1).getTier(), 3);
+        assertEquals(itemUnitDetails.get(1).getQuantity().intValue(), 60);
+        assertEquals(itemUnitDetails.get(1).getTierPrice().compareTo(new BigDecimal("100.00")), 0);
+    }
+
+    private List<InvoiceItem> produceInvoiceItems(List<RawUsage> rawUsages, UsageDetailMode usageDetailMode, List<InvoiceItem> existingItems) throws CatalogApiException, InvoiceApiException {
 
         final LocalDate startDate = new LocalDate(2014, 03, 20);
         final LocalDate firstBCDDate = new LocalDate(2014, 04, 15);
@@ -349,12 +412,12 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
 
         final LocalDate targetDate = endDate;
 
-        final BillingEvent event1 = createMockBillingEvent(startDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
+        final BillingEvent event1 = createMockBillingEvent(startDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
         final BillingEvent event2 = createMockBillingEvent(endDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
 
-        final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, rawUsages, targetDate, true, usageDetailMode, event1, event2);
+        final ContiguousIntervalCapacityUsageInArrear intervalCapacityInArrear = createContiguousIntervalCapacityInArrear(usage, rawUsages, targetDate, true, usageDetailMode, event1, event2);
 
-        final UsageInArrearItemsAndNextNotificationDate usageResult = intervalConsumableInArrear.computeMissingItemsAndNextNotificationDate(ImmutableList.<InvoiceItem>of());
+        final UsageInArrearItemsAndNextNotificationDate usageResult = intervalCapacityInArrear.computeMissingItemsAndNextNotificationDate(existingItems);
         final List<InvoiceItem> rawResults = usageResult.getInvoiceItems();
         final List<InvoiceItem> result = ImmutableList.copyOf(Iterables.filter(rawResults, new Predicate<InvoiceItem>() {
             @Override
@@ -363,7 +426,7 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
             }
         }));
 
-        for (InvoiceItem item: result) {
+        for (InvoiceItem item : result) {
             assertEquals(item.getCurrency(), Currency.BTC);
             assertEquals(item.getAccountId(), accountId);
             assertEquals(item.getBundleId(), bundleId);
@@ -378,5 +441,4 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
         return result;
     }
 
-
 }
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java
index 3c63a05..c7ebfa4 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java
@@ -22,10 +22,7 @@ import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Map;
 
 import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
@@ -37,11 +34,14 @@ import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.catalog.api.TierBlockPolicy;
 import org.killbill.billing.catalog.api.Usage;
+import org.killbill.billing.catalog.api.UsageType;
+import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.model.FixedPriceInvoiceItem;
 import org.killbill.billing.invoice.model.UsageInvoiceItem;
-import org.killbill.billing.invoice.usage.ContiguousIntervalUsageInArrear.UsageInArrearDetail;
 import org.killbill.billing.invoice.usage.ContiguousIntervalUsageInArrear.UsageInArrearItemsAndNextNotificationDate;
+import org.killbill.billing.invoice.usage.details.UsageConsumableInArrearDetail;
+import org.killbill.billing.invoice.usage.details.UsageConsumableInArrearTierUnitDetail;
 import org.killbill.billing.junction.BillingEvent;
 import org.killbill.billing.usage.RawUsage;
 import org.killbill.billing.usage.api.RolledUpUsage;
@@ -52,10 +52,7 @@ import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.base.Function;
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
@@ -64,6 +61,7 @@ import com.google.common.collect.Lists;
 import com.google.common.collect.Ordering;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertTrue;
 
 public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearBase {
@@ -143,13 +141,13 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
 
         final LocalDate targetDate = new LocalDate(2014, 03, 20);
 
-        final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
+        final ContiguousIntervalConsumableUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
                                                                                                                       createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
                                                                                                                                              BillingPeriod.MONTHLY,
                                                                                                                                              Collections.<Usage>emptyList())
                                                                                                                      );
 
-        List<UsageInArrearDetail> result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 111L));
+        List<UsageConsumableInArrearTierUnitDetail> result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 111L), ImmutableList.<UsageConsumableInArrearTierUnitDetail>of(), true);
         assertEquals(result.size(), 3);
         // 111 = 10 (tier1) + 100 (tier2) + 1 (tier3) => 10 * 1.5 + 100 * 1 + 1 * 0.5 = 115.5
         assertEquals(result.get(0).getAmount(), new BigDecimal("15.0"));
@@ -171,13 +169,13 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
 
         final LocalDate targetDate = new LocalDate(2014, 03, 20);
 
-        final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
+        final ContiguousIntervalConsumableUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
                                                                                                                       createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
                                                                                                                                              BillingPeriod.MONTHLY,
                                                                                                                                              Collections.<Usage>emptyList())
                                                                                                                      );
 
-        List<UsageInArrearDetail> result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 5325L));
+        List<UsageConsumableInArrearTierUnitDetail> result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 5325L), ImmutableList.<UsageConsumableInArrearTierUnitDetail>of(), true);
         assertEquals(result.size(), 2);
 
         // 5000 = 1000 (tier1) + 4325 (tier2) => 10 + 5 = 15
@@ -203,7 +201,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
 
         final LocalDate targetDate = new LocalDate(2014, 03, 20);
 
-        final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
+        final ContiguousIntervalConsumableUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
                                                                                                                       createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
                                                                                                                                              BillingPeriod.MONTHLY,
                                                                                                                                              Collections.<Usage>emptyList())
@@ -211,23 +209,23 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         //
         // In this model unit amount is first used to figure out which tier we are in, and then we price all unit at that 'target' tier
         //
-        List<UsageInArrearDetail> inputTier1 = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 1000L));
+        List<UsageConsumableInArrearTierUnitDetail> inputTier1 = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 1000L), ImmutableList.<UsageConsumableInArrearTierUnitDetail>of(), true);
         assertEquals(inputTier1.size(), 1);
         // 1000 units => (tier1) : 1000 / 100 + 1000 % 100 = 10
         assertEquals(inputTier1.get(0).getAmount(), new BigDecimal("10"));
 
-        List<UsageInArrearDetail> inputTier2 = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 101000L));
+        List<UsageConsumableInArrearTierUnitDetail> inputTier2 = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 101000L), ImmutableList.<UsageConsumableInArrearTierUnitDetail>of(), true);
         assertEquals(inputTier2.size(), 1);
         // 101000 units => (tier2) :  101000 / 1000 + 101000 % 1000 = 101 + 0 = 101
         assertEquals(inputTier2.get(0).getAmount(), new BigDecimal("101"));
 
-        List<UsageInArrearDetail> inputTier3 = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 101001L));
+        List<UsageConsumableInArrearTierUnitDetail> inputTier3 = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 101001L), ImmutableList.<UsageConsumableInArrearTierUnitDetail>of(), true);
         assertEquals(inputTier3.size(), 1);
         // 101001 units => (tier3) : 101001 / 1000 + 101001 % 1000 = 101 + 1 = 102 units => $51
         assertEquals(inputTier3.get(0).getAmount(), new BigDecimal("51.0"));
 
         // If we pass the maximum of the last tier, we price all units at the last tier
-        List<UsageInArrearDetail> inputLastTier = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 300000L));
+        List<UsageConsumableInArrearTierUnitDetail> inputLastTier = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 300000L), ImmutableList.<UsageConsumableInArrearTierUnitDetail>of(), true);
         assertEquals(inputLastTier.size(), 1);
         // 300000 units => (tier3) : 300000 / 1000 + 300000 % 1000 = 300 units => $150
         assertEquals(inputLastTier.get(0).getAmount(), new BigDecimal("150.0"));
@@ -250,13 +248,13 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
 
         final LocalDate targetDate = new LocalDate(2014, 03, 20);
 
-        final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
+        final ContiguousIntervalConsumableUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
                                                                                                                       createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
                                                                                                                                              BillingPeriod.MONTHLY,
                                                                                                                                              Collections.<Usage>emptyList())
                                                                                                                      );
 
-        List<UsageInArrearDetail> result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 111L));
+        List<UsageConsumableInArrearTierUnitDetail> result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 111L), ImmutableList.<UsageConsumableInArrearTierUnitDetail>of(), true);
         assertEquals(result.size(), 1);
 
         // 111 = 111 * 0.5 =
@@ -267,7 +265,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
 
 
     @Test(groups = "fast")
-    public void testComputeMissingItems() throws CatalogApiException, IOException {
+    public void testComputeMissingItems() throws CatalogApiException, IOException, InvoiceApiException {
 
         final LocalDate startDate = new LocalDate(2014, 03, 20);
         final LocalDate firstBCDDate = new LocalDate(2014, 04, 15);
@@ -321,11 +319,12 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         assertTrue(result.get(0).getStartDate().compareTo(startDate) == 0);
         assertTrue(result.get(0).getEndDate().compareTo(firstBCDDate) == 0);
 
-        // check item detail
-        List<UsageInArrearDetail> itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
-
+        assertNotNull(result.get(0).getItemDetails());
+        UsageConsumableInArrearDetail usageDetail = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageConsumableInArrearDetail>() {});
+        List<UsageConsumableInArrearTierUnitDetail> itemDetails = usageDetail.getTierDetails();
         assertEquals(itemDetails.size(), 1);
-        assertEquals(itemDetails.get(0).getAmount().compareTo(result.get(0).getAmount()),0);
+        // Because we did not have the details before, the new details don't take into account the
+        assertEquals(itemDetails.get(0).getAmount().compareTo(new BigDecimal("5.0")), 0);
 
         // Invoiced for 1 BTC and used 199  => 2 blocks => 2 BTC so remaining piece should be 1 BTC
         assertEquals(result.get(1).getAmount().compareTo(new BigDecimal("1.0")), 0, String.format("%s != 1.0", result.get(0).getAmount()));
@@ -338,11 +337,13 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         assertEquals(result.get(1).getUsageName(), usage.getName());
         assertTrue(result.get(1).getStartDate().compareTo(firstBCDDate) == 0);
         assertTrue(result.get(1).getEndDate().compareTo(endDate) == 0);
+        assertNotNull(result.get(1).getItemDetails());
+
+        usageDetail = objectMapper.readValue(result.get(1).getItemDetails(), new TypeReference<UsageConsumableInArrearDetail>() {});
+        itemDetails = usageDetail.getTierDetails();
+        assertEquals(itemDetails.size(), 1);
+        assertEquals(itemDetails.get(0).getAmount().compareTo(new BigDecimal("2.0")), 0);
 
-        // check item detail
-        List<UsageInArrearDetail> itemDetails2 = objectMapper.readValue(result.get(1).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
-        assertEquals(itemDetails2.size(), 1);
-        assertEquals(itemDetails2.get(0).getAmount().compareTo(result.get(1).getAmount()),0);
     }
 
     @Test(groups = "fast")
@@ -436,7 +437,11 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         final BillingEvent event2 = createMockBillingEvent(new LocalDate(2014, 10, 16).toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
 
 
-        final ContiguousIntervalUsageInArrear intervalConsumableInArrear = new ContiguousIntervalUsageInArrear(usage, accountId, invoiceId, rawUsages, targetDate, rawUsageStartDate, usageDetailMode, internalCallContext);
+
+        final ContiguousIntervalUsageInArrear intervalConsumableInArrear = usage.getUsageType() == UsageType.CAPACITY ?
+                                                                           new ContiguousIntervalCapacityUsageInArrear(usage, accountId, invoiceId, rawUsages, targetDate, rawUsageStartDate, usageDetailMode, internalCallContext) :
+                                                                           new ContiguousIntervalConsumableUsageInArrear(usage, accountId, invoiceId, rawUsages, targetDate, rawUsageStartDate, usageDetailMode, internalCallContext);
+
         intervalConsumableInArrear.addBillingEvent(event1);
         intervalConsumableInArrear.addBillingEvent(event2);
 
@@ -445,7 +450,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
     }
 
     @Test(groups = "fast")
-    public void testTobeBilledForUnit() throws CatalogApiException {
+    public void testBilledUsage() throws CatalogApiException {
 
         final DefaultTieredBlock block1 = createDefaultTieredBlock("cell-phone-minutes", 1000, 10000, new BigDecimal("0.5"));
         final DefaultTieredBlock block2 = createDefaultTieredBlock("Mbytes", 512, 512000, new BigDecimal("0.3"));
@@ -453,32 +458,36 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
 
         final DefaultUsage usage = createConsumableInArrearUsage(usageName, BillingPeriod.MONTHLY, TierBlockPolicy.ALL_TIERS, tier);
         final LocalDate targetDate = new LocalDate(2014, 03, 20);
-        final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
+        final ContiguousIntervalConsumableUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
                                                                                                                       createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
                                                                                                                                              BillingPeriod.MONTHLY,
                                                                                                                                              Collections.<Usage>emptyList())
                                                                                                                      );
-        List<UsageInArrearDetail> results = Lists.newArrayList();
-        results.addAll(intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("cell-phone-minutes", 1000L)));
-        results.addAll(intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("Mbytes", 30720L)));
-        assertEquals(results.size(), 2);
+        final List<UsageConsumableInArrearTierUnitDetail> tierUnitDetails = Lists.newArrayList();
+        tierUnitDetails.addAll(intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("cell-phone-minutes", 1000L), ImmutableList.<UsageConsumableInArrearTierUnitDetail>of(), true));
+        tierUnitDetails.addAll(intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("Mbytes", 30720L), ImmutableList.<UsageConsumableInArrearTierUnitDetail>of(), true));
+        assertEquals(tierUnitDetails.size(), 2);
 
-        assertEquals(intervalConsumableInArrear.toBeBilledForUnit(results), new BigDecimal("18.5"));
+        final UsageConsumableInArrearDetail details = new UsageConsumableInArrearDetail(tierUnitDetails);
+
+        assertEquals(details.getAmount().compareTo(new BigDecimal("18.5")), 0);
     }
 
     @Test(groups = "fast")
-    public void testComputeMissingItemsAggregateModeAllTier() throws CatalogApiException, IOException {
+    public void testComputeMissingItemsAggregateModeAllTier() throws CatalogApiException, IOException, InvoiceApiException {
 
         // Case 1
         List<RawUsage> rawUsages = new ArrayList<RawUsage>();
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 99L));
 
-        List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.AGGREGATE);
+        List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.AGGREGATE, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 1);
-        assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("203")),0);
+        assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("203")), 0);
+
+        UsageConsumableInArrearDetail usageDetail = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageConsumableInArrearDetail>() {});
+        List<UsageConsumableInArrearTierUnitDetail> itemDetails = usageDetail.getTierDetails();
 
-        List<UsageInArrearDetail> itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
         // BAR: 99 * 2 = 198
         assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
         assertEquals(itemDetails.get(0).getTier(), 1);
@@ -497,11 +506,13 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
 
-        result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.AGGREGATE);
+        result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.AGGREGATE, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 1);
-        assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("225")),0);
+        assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("225")), 0);
+
+        usageDetail = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageConsumableInArrearDetail>() {});
+        itemDetails = usageDetail.getTierDetails();
 
-        itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
         // BAR: 100 * 2 = 200
         assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
         assertEquals(itemDetails.get(0).getTier(), 1);
@@ -526,11 +537,12 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 75L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
 
-        result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.AGGREGATE);
+        result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.AGGREGATE, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 1);
-        assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("2230")),0);
+        assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("2230")), 0);
 
-        itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
+        usageDetail = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageConsumableInArrearDetail>() {});
+        itemDetails = usageDetail.getTierDetails();
         // BAR: 100 * 2 = 200
         assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
         assertEquals(itemDetails.get(0).getTier(), 1);
@@ -564,14 +576,14 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
     }
 
     @Test(groups = "fast")
-    public void testComputeMissingItemsDetailModeAllTier() throws CatalogApiException, IOException {
+    public void testComputeMissingItemsDetailModeAllTier() throws CatalogApiException, IOException, InvoiceApiException {
 
         // Case 1
         List<RawUsage> rawUsages = new ArrayList<RawUsage>();
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 99L));
 
-        List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.DETAIL);
+        List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.DETAIL, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 2);
         // BAR: 99 * 2 = 198
         assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("198")), 0);
@@ -587,7 +599,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
 
-        result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.DETAIL);
+        result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.DETAIL, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 3);
        // BAR: 100 * 2 = 200
         assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("200.0")), 0);
@@ -607,7 +619,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 75L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
 
-        result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.DETAIL);
+        result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.DETAIL, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 5);
        // BAR: 100 * 2 = 200
         assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("200.0")), 0);
@@ -632,18 +644,19 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
     }
 
     @Test(groups = "fast")
-    public void testComputeMissingItemsAggregateModeTopTier() throws CatalogApiException, IOException {
+    public void testComputeMissingItemsAggregateModeTopTier() throws CatalogApiException, IOException, InvoiceApiException {
 
         // Case 1
         List<RawUsage> rawUsages = new ArrayList<RawUsage>();
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 99L));
 
-        List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.AGGREGATE);
+        List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.AGGREGATE, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 1);
-        assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("203")),0);
+        assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("203")), 0);
 
-        List<UsageInArrearDetail> itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
+        UsageConsumableInArrearDetail usageDetail = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageConsumableInArrearDetail>() {});
+        List<UsageConsumableInArrearTierUnitDetail> itemDetails = usageDetail.getTierDetails();
         // BAR: 99 * 2 = 198
         assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
         assertEquals(itemDetails.get(0).getTier(), 1);
@@ -662,11 +675,13 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
 
-        result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.AGGREGATE);
+        result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.AGGREGATE, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 1);
-        assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("2025")),0);
+        assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("2025")), 0);
+
+        usageDetail = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageConsumableInArrearDetail>() {});
+        itemDetails = usageDetail.getTierDetails();
 
-        itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
         // BAR: 101 * 20 = 2020
         assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
         assertEquals(itemDetails.get(0).getTier(), 2);
@@ -685,11 +700,12 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 76L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
 
-        result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.AGGREGATE);
+        result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.AGGREGATE, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 1);
-        assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("9620")),0);
+        assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("9620")), 0);
 
-        itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
+        usageDetail = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageConsumableInArrearDetail>() {});
+        itemDetails = usageDetail.getTierDetails();
         // BAR: 101 * 20 = 2020
         assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
         assertEquals(itemDetails.get(0).getTier(), 2);
@@ -705,14 +721,14 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
     }
 
     @Test(groups = "fast")
-    public void testComputeMissingItemsDetailModeTopTier() throws CatalogApiException, IOException {
+    public void testComputeMissingItemsDetailModeTopTier() throws CatalogApiException, IOException, InvoiceApiException {
 
         // Case 1
         List<RawUsage> rawUsages = new ArrayList<RawUsage>();
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 99L));
 
-        List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.DETAIL);
+        List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.DETAIL, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 2);
         // BAR: 99 * 2 = 198
         assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("198")), 0);
@@ -728,9 +744,9 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
 
-        result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.DETAIL);
+        result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.DETAIL, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 2);
-         // BAR: 101 * 20 = 2020
+        // BAR: 101 * 20 = 2020
         assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("2020.0")), 0);
         assertEquals(result.get(0).getQuantity().intValue(), 101);
         assertEquals(result.get(0).getRate().compareTo(new BigDecimal("20.0")), 0);
@@ -744,7 +760,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 76L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
 
-        result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.DETAIL);
+        result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.DETAIL, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 2);
         // BAR: 101 * 20 = 2020
         assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("2020.0")), 0);
@@ -756,7 +772,82 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         assertEquals(result.get(1).getRate().compareTo(new BigDecimal("100.0")), 0);
     }
 
-    private List<InvoiceItem> produceInvoiceItems(List<RawUsage> rawUsages, TierBlockPolicy tierBlockPolicy, UsageDetailMode usageDetailMode) throws CatalogApiException {
+
+
+    @Test(groups = "fast")
+    public void testMultipleItemsAndTiersWithExistingItemsAllTiers() throws CatalogApiException, IOException, InvoiceApiException {
+
+        //
+        // Let's assume we were already billed on the previous period
+        //
+        // FOO : 10 (tier 1) + 40 (tier 2) = 50
+        final UsageConsumableInArrearTierUnitDetail existingFooUsageTier1 = new UsageConsumableInArrearTierUnitDetail(1, "FOO", BigDecimal.ONE, 1, 10, new BigDecimal("10.00"));
+        final UsageConsumableInArrearTierUnitDetail existingFooUsageTier2 = new UsageConsumableInArrearTierUnitDetail(2, "FOO", BigDecimal.TEN, 1, 40, new BigDecimal("400.00"));
+        // BAR : 10 (tier 1) + 40 (tier 2)
+        final UsageConsumableInArrearTierUnitDetail existingBarUsageTier1 = new UsageConsumableInArrearTierUnitDetail(1, "BAR", new BigDecimal("2.00"), 1, 80, new BigDecimal("160.00"));
+
+        final List<UsageConsumableInArrearTierUnitDetail> existingUsage = ImmutableList.of(existingFooUsageTier1, existingFooUsageTier2, existingBarUsageTier1);
+
+        final UsageConsumableInArrearDetail usageConsumableInArrearDetail = new UsageConsumableInArrearDetail(existingUsage);
+
+        final String existingUsageJson =  objectMapper.writeValueAsString(usageConsumableInArrearDetail);
+
+        //
+        // Create usage data points (will include already billed + add new usage data)
+        //
+        List<RawUsage> rawUsages = new ArrayList<RawUsage>();
+        rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 50L /* already built */ + 20L)); // tier 3
+        rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 80L /* already built */ + 120L)); // tier 2
+
+        final List<InvoiceItem> existingItems = new ArrayList<InvoiceItem>();
+        final InvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, new LocalDate(2014, 03, 20), new LocalDate(2014, 04, 15), new BigDecimal("570.00"), null, currency, null, existingUsageJson);
+        existingItems.add(ii1);
+
+        List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.AGGREGATE, existingItems);
+        assertEquals(result.size(), 1);
+        assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("3140.00")), 0, String.format("%s != 3140.0", result.get(0).getAmount()));
+
+        UsageConsumableInArrearDetail usageDetail = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageConsumableInArrearDetail>() {});
+        List<UsageConsumableInArrearTierUnitDetail> itemDetails = usageDetail.getTierDetails();
+
+        // BAR item detail
+        assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
+        assertEquals(itemDetails.get(0).getTier(), 1);
+        assertEquals(itemDetails.get(0).getTierBlockSize(), 1);
+        assertEquals(itemDetails.get(0).getQuantity().intValue(), 20);
+        assertEquals(itemDetails.get(0).getTierPrice().compareTo(new BigDecimal("2.00")), 0);
+        assertEquals(itemDetails.get(0).getAmount().compareTo(new BigDecimal("40.00")), 0);
+
+        assertEquals(itemDetails.get(1).getTierUnit(), "BAR");
+        assertEquals(itemDetails.get(1).getTier(), 2);
+        assertEquals(itemDetails.get(1).getTierBlockSize(), 1);
+        assertEquals(itemDetails.get(1).getQuantity().intValue(), 100);
+        assertEquals(itemDetails.get(1).getTierPrice().compareTo(new BigDecimal("20.00")), 0);
+        assertEquals(itemDetails.get(1).getAmount().compareTo(new BigDecimal("2000.00")), 0);
+
+        // FOO item detail
+        assertEquals(itemDetails.get(2).getTierUnit(), "FOO");
+        assertEquals(itemDetails.get(2).getTier(), 2);
+        assertEquals(itemDetails.get(2).getTierBlockSize(), 1);
+        assertEquals(itemDetails.get(2).getQuantity().intValue(), 10);
+        assertEquals(itemDetails.get(2).getTierPrice().compareTo(new BigDecimal("10.00")), 0);
+        assertEquals(itemDetails.get(2).getAmount().compareTo(new BigDecimal("100.00")), 0);
+
+        assertEquals(itemDetails.get(3).getTierUnit(), "FOO");
+        assertEquals(itemDetails.get(3).getTier(), 3);
+        assertEquals(itemDetails.get(3).getTierBlockSize(), 1);
+        assertEquals(itemDetails.get(3).getQuantity().intValue(), 10);
+        assertEquals(itemDetails.get(3).getTierPrice().compareTo(new BigDecimal("100.00")), 0);
+        assertEquals(itemDetails.get(3).getAmount().compareTo(new BigDecimal("1000.00")), 0);
+    }
+
+
+    @Test(groups = "fast")
+    public void testMultipleItemsAndTiersWithExistingItemsTopTier() throws CatalogApiException, IOException {
+        // TODO + code
+    }
+
+    private List<InvoiceItem> produceInvoiceItems(List<RawUsage> rawUsages, TierBlockPolicy tierBlockPolicy, UsageDetailMode usageDetailMode, final List<InvoiceItem> existingItems) throws CatalogApiException, InvoiceApiException {
 
         final LocalDate startDate = new LocalDate(2014, 03, 20);
         final LocalDate firstBCDDate = new LocalDate(2014, 04, 15);
@@ -782,7 +873,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
 
         final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, rawUsages, targetDate, true, usageDetailMode, event1, event2);
 
-        final UsageInArrearItemsAndNextNotificationDate usageResult = intervalConsumableInArrear.computeMissingItemsAndNextNotificationDate(ImmutableList.<InvoiceItem>of());
+        final UsageInArrearItemsAndNextNotificationDate usageResult = intervalConsumableInArrear.computeMissingItemsAndNextNotificationDate(existingItems);
         final List<InvoiceItem> rawResults = usageResult.getInvoiceItems();
         final List<InvoiceItem> result = ImmutableList.copyOf(Iterables.filter(rawResults, new Predicate<InvoiceItem>() {
             @Override
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java
index f5c5c60..42c1709 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java
@@ -22,7 +22,6 @@ import java.util.List;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.catalog.DefaultInternationalPrice;
@@ -45,11 +44,10 @@ import org.killbill.billing.junction.BillingEvent;
 import org.killbill.billing.subscription.api.SubscriptionBase;
 import org.killbill.billing.usage.RawUsage;
 import org.killbill.billing.util.config.definition.InvoiceConfig.UsageDetailMode;
+import org.killbill.billing.util.jackson.ObjectMapper;
 import org.mockito.Mockito;
 import org.testng.annotations.BeforeClass;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
-
 public abstract class TestUsageInArrearBase extends InvoiceTestSuiteNoDB {
 
     protected int BCD;
@@ -80,12 +78,27 @@ public abstract class TestUsageInArrearBase extends InvoiceTestSuiteNoDB {
         objectMapper = new ObjectMapper();
     }
 
-    protected ContiguousIntervalUsageInArrear createContiguousIntervalConsumableInArrear(final DefaultUsage usage, final List<RawUsage> rawUsages, final LocalDate targetDate, final boolean closedInterval, final BillingEvent... events) {
+
+    protected ContiguousIntervalCapacityUsageInArrear createContiguousIntervalCapacityInArrear(final DefaultUsage usage, final List<RawUsage> rawUsages, final LocalDate targetDate, final boolean closedInterval, final BillingEvent... events) {
+        return createContiguousIntervalCapacityInArrear(usage, rawUsages, targetDate, closedInterval, usageDetailMode, events);
+    }
+
+    protected ContiguousIntervalCapacityUsageInArrear createContiguousIntervalCapacityInArrear(final DefaultUsage usage, final List<RawUsage> rawUsages, final LocalDate targetDate, final boolean closedInterval, UsageDetailMode detailMode, final BillingEvent... events) {
+        final ContiguousIntervalCapacityUsageInArrear intervalCapacityInArrear = new ContiguousIntervalCapacityUsageInArrear(usage, accountId, invoiceId, rawUsages, targetDate, new LocalDate(events[0].getEffectiveDate()),  detailMode, internalCallContext);
+        for (final BillingEvent event : events) {
+            intervalCapacityInArrear.addBillingEvent(event);
+        }
+        intervalCapacityInArrear.build(closedInterval);
+        return intervalCapacityInArrear;
+    }
+
+
+    protected ContiguousIntervalConsumableUsageInArrear createContiguousIntervalConsumableInArrear(final DefaultUsage usage, final List<RawUsage> rawUsages, final LocalDate targetDate, final boolean closedInterval, final BillingEvent... events) {
         return createContiguousIntervalConsumableInArrear(usage, rawUsages, targetDate, closedInterval, usageDetailMode, events);
     }
 
-    protected ContiguousIntervalUsageInArrear createContiguousIntervalConsumableInArrear(final DefaultUsage usage, final List<RawUsage> rawUsages, final LocalDate targetDate, final boolean closedInterval, UsageDetailMode detailMode, final BillingEvent... events) {
-        final ContiguousIntervalUsageInArrear intervalConsumableInArrear = new ContiguousIntervalUsageInArrear(usage, accountId, invoiceId, rawUsages, targetDate, new LocalDate(events[0].getEffectiveDate()), detailMode, internalCallContext);
+    protected ContiguousIntervalConsumableUsageInArrear createContiguousIntervalConsumableInArrear(final DefaultUsage usage, final List<RawUsage> rawUsages, final LocalDate targetDate, final boolean closedInterval, UsageDetailMode detailMode, final BillingEvent... events) {
+        final ContiguousIntervalConsumableUsageInArrear intervalConsumableInArrear = new ContiguousIntervalConsumableUsageInArrear(usage, accountId, invoiceId, rawUsages, targetDate, new LocalDate(events[0].getEffectiveDate()), detailMode, internalCallContext);
         for (final BillingEvent event : events) {
             intervalConsumableInArrear.addBillingEvent(event);
         }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TestResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TestResource.java
index a73a9b9..5e01d3c 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TestResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TestResource.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -18,12 +18,15 @@
 
 package org.killbill.billing.jaxrs.resources;
 
+import java.util.Iterator;
+
 import javax.inject.Inject;
 import javax.servlet.ServletRequest;
 import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.DELETE;
 import javax.ws.rs.DefaultValue;
 import javax.ws.rs.GET;
+import javax.ws.rs.HEAD;
 import javax.ws.rs.HeaderParam;
 import javax.ws.rs.POST;
 import javax.ws.rs.PUT;
@@ -253,12 +256,21 @@ public class TestResource extends JaxRsResourceBase {
 
     private boolean areAllNotificationsProcessed(final Long tenantRecordId) {
         int nbNotifications = 0;
-        for (final NotificationQueue notificationQueue : notificationQueueService.getNotificationQueues()) {
-            for (final NotificationEventWithMetadata<NotificationEvent> notificationEvent : notificationQueue.getFutureOrInProcessingNotificationForSearchKey2(null, tenantRecordId)) {
-                if (!notificationEvent.getEffectiveDate().isAfter(clock.getUTCNow())) {
-                    nbNotifications += 1;
+        final Iterator<NotificationQueue> iterator = notificationQueueService.getNotificationQueues().iterator();
+        try {
+            while (iterator.hasNext()) {
+                final NotificationQueue notificationQueue = iterator.next();
+                for (final NotificationEventWithMetadata<NotificationEvent> notificationEvent : notificationQueue.getFutureOrInProcessingNotificationForSearchKey2(null, tenantRecordId)) {
+                    if (!notificationEvent.getEffectiveDate().isAfter(clock.getUTCNow())) {
+                        nbNotifications += 1;
+                    }
                 }
             }
+        } finally {
+            // Go through all results to close the connection
+            while (iterator.hasNext()) {
+                iterator.next();
+            }
         }
         if (nbNotifications != 0) {
             log.info("TestResource: {} queue(s) with more notification(s) to process", nbNotifications);
@@ -268,6 +280,7 @@ public class TestResource extends JaxRsResourceBase {
 
     private boolean areAllBusEventsProcessed(final Long tenantRecordId) {
         final Iterable<BusEventWithMetadata<BusEvent>> availableBusEventForSearchKey2 = persistentBus.getAvailableOrInProcessingBusEventsForSearchKey2(null, tenantRecordId);
+        // This will go through all results to close the connection
         final int nbBusEvents = Iterables.size(availableBusEventForSearchKey2);
         if (nbBusEvents != 0) {
             log.info("TestResource: at least {} more bus event(s) to process", nbBusEvents);

NEWS 8(+7 -1)

diff --git a/NEWS b/NEWS
index 4827589..1be25b9 100644
--- a/NEWS
+++ b/NEWS
@@ -3,7 +3,13 @@
     Fix connection leak (#558)
     Fix limitation where catalog plan name cannot end with an number (#842)
     Fix missing Invoice Notification when we have future billing events (#846)
-    Rreduce log level of InvoiceItemGeneratorLogger (#851)
+    Reduce log level of InvoiceItemGeneratorLogger (#851)
+
+0.18.17
+    Relax sanity checks for STANDALONE subscriptions #840
+    Fix JDBC connection leak in pagination API #853
+    Fix limitation where catalog plan name cannot end with an number #842
+    Reduce log level of InvoiceItemGeneratorLogger #851
 
 0.18.16
     See https://github.com/killbill/killbill/releases/tag/killbill-0.18.16
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/notification/DefaultOverduePosterBase.java b/overdue/src/main/java/org/killbill/billing/overdue/notification/DefaultOverduePosterBase.java
index d06c167..fc14f47 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/notification/DefaultOverduePosterBase.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/notification/DefaultOverduePosterBase.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -18,6 +18,7 @@
 
 package org.killbill.billing.overdue.notification;
 
+import java.util.Iterator;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
@@ -93,10 +94,18 @@ public abstract class DefaultOverduePosterBase implements OverduePoster {
                 public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
                     final Iterable<NotificationEventWithMetadata<T>> futureNotifications = getFutureNotificationsForAccountInTransaction(entitySqlDaoWrapperFactory, checkOverdueQueue,
                                                                                                                                          clazz, context);
-                    for (final NotificationEventWithMetadata<T> notification : futureNotifications) {
-                        checkOverdueQueue.removeNotificationFromTransaction(entitySqlDaoWrapperFactory.getHandle().getConnection(), notification.getRecordId());
+                    final Iterator<NotificationEventWithMetadata<T>> iterator = futureNotifications.iterator();
+                    try {
+                        while (iterator.hasNext()) {
+                            final NotificationEventWithMetadata<T> notification = iterator.next();
+                            checkOverdueQueue.removeNotificationFromTransaction(entitySqlDaoWrapperFactory.getHandle().getConnection(), notification.getRecordId());
+                        }
+                    } finally {
+                        // Go through all results to close the connection
+                        while (iterator.hasNext()) {
+                            iterator.next();
+                        }
                     }
-
                     return null;
                 }
             });
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueCheckPoster.java b/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueCheckPoster.java
index 25d7eff..3fc227a 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueCheckPoster.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueCheckPoster.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -18,6 +18,8 @@
 
 package org.killbill.billing.overdue.notification;
 
+import java.util.Iterator;
+
 import org.joda.time.DateTime;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
@@ -48,23 +50,32 @@ public class OverdueCheckPoster extends DefaultOverduePosterBase {
         boolean shouldInsertNewNotification = true;
         int minIndexToDeleteFrom = 0;
         int index = 0;
-        for (final NotificationEventWithMetadata<T> cur : futureNotifications) {
-            // Results are ordered by effective date asc
-            if (index == 0) {
-                if (cur.getEffectiveDate().isBefore(futureNotificationTime)) {
-                    // We don't have to insert a new one. For sanity, delete any other future notification
-                    minIndexToDeleteFrom = 1;
-                    shouldInsertNewNotification = false;
-                } else {
-                    // We win - we are before any other already recorded. Delete all others.
-                    minIndexToDeleteFrom = 0;
+        final Iterator<NotificationEventWithMetadata<T>> iterator = futureNotifications.iterator();
+        try {
+            while (iterator.hasNext()) {
+                final NotificationEventWithMetadata<T> cur = iterator.next();
+                // Results are ordered by effective date asc
+                if (index == 0) {
+                    if (cur.getEffectiveDate().isBefore(futureNotificationTime)) {
+                        // We don't have to insert a new one. For sanity, delete any other future notification
+                        minIndexToDeleteFrom = 1;
+                        shouldInsertNewNotification = false;
+                    } else {
+                        // We win - we are before any other already recorded. Delete all others.
+                        minIndexToDeleteFrom = 0;
+                    }
                 }
-            }
 
-            if (minIndexToDeleteFrom <= index) {
-                overdueQueue.removeNotificationFromTransaction(entitySqlDaoWrapperFactory.getHandle().getConnection(), cur.getRecordId());
+                if (minIndexToDeleteFrom <= index) {
+                    overdueQueue.removeNotificationFromTransaction(entitySqlDaoWrapperFactory.getHandle().getConnection(), cur.getRecordId());
+                }
+                index++;
+            }
+        } finally {
+            // Go through all results to close the connection
+            while (iterator.hasNext()) {
+                iterator.next();
             }
-            index++;
         }
 
         return shouldInsertNewNotification;
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/notification/TestDefaultOverdueCheckPoster.java b/overdue/src/test/java/org/killbill/billing/overdue/notification/TestDefaultOverdueCheckPoster.java
index 1a7b3bd..ca991a2 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/notification/TestDefaultOverdueCheckPoster.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/notification/TestDefaultOverdueCheckPoster.java
@@ -92,6 +92,7 @@ public class TestDefaultOverdueCheckPoster extends OverdueTestSuiteWithEmbeddedD
         return entitySqlDaoTransactionalJdbiWrapper.execute(new EntitySqlDaoTransactionWrapper<List<NotificationEventWithMetadata<OverdueCheckNotificationKey>>>() {
             @Override
             public List<NotificationEventWithMetadata<OverdueCheckNotificationKey>> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+                // This will go through all results to close the connection
                 return ImmutableList.<NotificationEventWithMetadata<OverdueCheckNotificationKey>>copyOf(((OverdueCheckPoster) checkPoster).getFutureNotificationsForAccountInTransaction(entitySqlDaoWrapperFactory, overdueQueue, OverdueCheckNotificationKey.class, internalCallContext));
             }
         });
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java
index 4bd018a..ecaa6d8 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -24,6 +24,7 @@ import java.util.Collection;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
@@ -411,11 +412,19 @@ public class PaymentProcessor extends ProcessorBase {
             final Iterable<NotificationEventWithMetadata<NotificationEvent>> notificationEventWithMetadatas =
                     retryQueue.getFutureNotificationForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId());
 
-            for (final NotificationEventWithMetadata<NotificationEvent> notificationEvent : notificationEventWithMetadatas) {
-                if (((PaymentRetryNotificationKey) notificationEvent.getEvent()).getAttemptId().equals(lastPaymentAttemptId)) {
-                    retryQueue.removeNotification(notificationEvent.getRecordId());
+            final Iterator<NotificationEventWithMetadata<NotificationEvent>> iterator = notificationEventWithMetadatas.iterator();
+            try {
+                while (iterator.hasNext()) {
+                    final NotificationEventWithMetadata<NotificationEvent> notificationEvent = iterator.next();
+                    if (((PaymentRetryNotificationKey) notificationEvent.getEvent()).getAttemptId().equals(lastPaymentAttemptId)) {
+                        retryQueue.removeNotification(notificationEvent.getRecordId());
+                    }
                 }
+            } finally {
                 // Go through all results to close the connection
+                while (iterator.hasNext()) {
+                    iterator.next();
+                }
             }
         } catch (final NoSuchNotificationQueue noSuchNotificationQueue) {
             log.error("ERROR Loading Notification Queue - " + noSuchNotificationQueue.getMessage());
diff --git a/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java b/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
index 8e0f0fe..27a8bde 100644
--- a/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
+++ b/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -26,6 +26,7 @@ import java.util.UUID;
 
 import org.joda.time.DateTime;
 import org.killbill.billing.account.api.Account;
+import org.killbill.billing.api.FlakyRetryAnalyzer;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.payment.PaymentTestSuiteWithEmbeddedDB;
 import org.killbill.billing.payment.api.Payment;
@@ -94,7 +95,8 @@ public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
         assertEquals(retrievedAttempts.get(0).getPluginName(), pluginName);
     }
 
-    @Test(groups = "slow")
+    // Flaky, see https://github.com/killbill/killbill/issues/860
+    @Test(groups = "slow", retryAnalyzer = FlakyRetryAnalyzer.class)
     public void testPaymentAndTransactions() {
         final UUID paymentMethodId = UUID.randomUUID();
         final UUID accountId = UUID.randomUUID();
@@ -293,7 +295,8 @@ public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
         assertEquals(deletedPaymentMethod.getPluginName(), pluginName);
     }
 
-    @Test(groups = "slow")
+    // Flaky, see https://github.com/killbill/killbill/issues/860
+    @Test(groups = "slow", retryAnalyzer = FlakyRetryAnalyzer.class)
     public void testPendingTransactions() {
 
         final UUID paymentMethodId = UUID.randomUUID();
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 d09c923..086918e 100644
--- a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
+++ b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -20,6 +20,7 @@ package org.killbill.billing.payment;
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
@@ -28,6 +29,7 @@ import java.util.concurrent.TimeUnit;
 
 import org.joda.time.LocalDate;
 import org.killbill.billing.account.api.Account;
+import org.killbill.billing.api.FlakyRetryAnalyzer;
 import org.killbill.billing.api.TestApiListener;
 import org.killbill.billing.api.TestApiListener.NextEvent;
 import org.killbill.billing.callcontext.InternalCallContext;
@@ -414,7 +416,8 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
     }
 
     // The test will check that when a PENDING entry stays PENDING, we go through all our retries and eventually give up (no infinite loop of retries)
-    @Test(groups = "slow")
+    // Flaky, see https://github.com/killbill/killbill/issues/860
+    @Test(groups = "slow", retryAnalyzer = FlakyRetryAnalyzer.class)
     public void testPendingEntriesThatDontMove() throws Exception {
 
         final BigDecimal requestedAmount = BigDecimal.TEN;
@@ -447,13 +450,13 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
             // Verify there is a notification to retry updating the value
             assertEquals(getPendingNotificationCnt(internalCallContext), 1);
 
-            clock.addDeltaFromReality(cur.getMillis() + 1);
+            clock.addDeltaFromReality(cur.getMillis() + 1000);
 
             assertNotificationsCompleted(internalCallContext, 5);
             // We add a sleep here to make sure the notification gets processed. Note that calling assertNotificationsCompleted alone would not work
             // because there is a point in time where the notification queue is empty (showing notification was processed), but the processing of the notification
             // will itself enter a new notification, and so the synchronization is difficult without writing *too much code*.
-            Thread.sleep(1000);
+            Thread.sleep(1500);
             assertNotificationsCompleted(internalCallContext, 5);
 
             final Payment updatedPayment = paymentApi.getPayment(payment.getId(), false, false, ImmutableList.<PluginProperty>of(), callContext);
@@ -510,11 +513,19 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
                 @Override
                 public Boolean call() throws Exception {
                     boolean completed = true;
-                    for (final NotificationEventWithMetadata<NotificationEvent> notificationEvent : notificationQueueService.getNotificationQueue(DefaultPaymentService.SERVICE_NAME, Janitor.QUEUE_NAME).getFutureOrInProcessingNotificationForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId())) {
-                        if (!notificationEvent.getEffectiveDate().isAfter(clock.getUTCNow())) {
-                            completed = false;
+                    final Iterator<NotificationEventWithMetadata<NotificationEvent>> iterator = notificationQueueService.getNotificationQueue(DefaultPaymentService.SERVICE_NAME, Janitor.QUEUE_NAME).getFutureOrInProcessingNotificationForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId()).iterator();
+                    try {
+                        while (iterator.hasNext()) {
+                            final NotificationEventWithMetadata<NotificationEvent> notificationEvent = iterator.next();
+                            if (!notificationEvent.getEffectiveDate().isAfter(clock.getUTCNow())) {
+                                completed = false;
+                            }
                         }
+                    } finally {
                         // Go through all results to close the connection
+                        while (iterator.hasNext()) {
+                            iterator.next();
+                        }
                     }
                     return completed;
                 }
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPushNotification.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPushNotification.java
index 4b32eba..5c53689 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPushNotification.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPushNotification.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -31,6 +31,7 @@ import javax.servlet.http.HttpServletResponse;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.servlet.ServletHolder;
+import org.killbill.billing.api.FlakyRetryAnalyzer;
 import org.killbill.billing.client.KillBillClientException;
 import org.killbill.billing.client.model.TenantKey;
 import org.killbill.billing.jaxrs.json.NotificationJson;
@@ -152,7 +153,8 @@ public class TestPushNotification extends TestJaxrsBase {
         return callback;
     }
 
-    @Test(groups = "slow")
+    // Flaky, see https://github.com/killbill/killbill/issues/860
+    @Test(groups = "slow", retryAnalyzer = FlakyRetryAnalyzer.class)
     public void testPushNotificationRetries() throws Exception {
         final String callback = registerTenantForCallback();
 
@@ -205,7 +207,8 @@ public class TestPushNotification extends TestJaxrsBase {
         unregisterTenantForCallback(callback);
     }
 
-    @Test(groups = "slow")
+    // Flaky, see https://github.com/killbill/killbill/issues/860
+    @Test(groups = "slow", retryAnalyzer = FlakyRetryAnalyzer.class)
     public void testPushNotificationRetriesMaxAttemptNumber() throws Exception {
         final String callback = registerTenantForCallback();
 
@@ -231,16 +234,16 @@ public class TestPushNotification extends TestJaxrsBase {
 
         resetCallbackStatusProperties();
 
-        // move clock 15 minutes and get 1st retry
-        clock.addDeltaFromReality(900000);
+        // move clock 15 minutes (+10s for flakiness) and get 1st retry
+        clock.addDeltaFromReality(910000);
 
         assertAllCallbacksCompleted();
         Assert.assertTrue(callbackCompletedWithError);
 
         resetCallbackStatusProperties();
 
-        // move clock an hour and get 2nd retry
-        clock.addDeltaFromReality(3600000);
+        // move clock an hour (+10s for flakiness) and get 2nd retry
+        clock.addDeltaFromReality(3610000);
 
         assertAllCallbacksCompleted();
         Assert.assertTrue(callbackCompletedWithError);
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
index 3af29b0..c56df6e 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
@@ -295,7 +295,8 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
 
             @Override
             public SubscriptionBaseBundle inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
-                final List<SubscriptionBundleModelDao> existingBundles = entitySqlDaoWrapperFactory.become(BundleSqlDao.class).getBundlesForLikeKey(bundle.getExternalKey(), context);
+                final List<SubscriptionBundleModelDao> existingBundles = bundle.getExternalKey() == null ? ImmutableList.<SubscriptionBundleModelDao>of()
+                                                                                                         : entitySqlDaoWrapperFactory.become(BundleSqlDao.class).getBundlesForLikeKey(bundle.getExternalKey(), context);
 
                 final SubscriptionBaseBundle unusedBundle = findExistingUnusedBundleForExternalKeyAndAccount(existingBundles, entitySqlDaoWrapperFactory);
                 if (unusedBundle != null) {
diff --git a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/BundleSqlDao.sql.stg b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/BundleSqlDao.sql.stg
index 369469b..ecfeb0f 100644
--- a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/BundleSqlDao.sql.stg
+++ b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/BundleSqlDao.sql.stg
@@ -49,15 +49,10 @@ where id = :id
 
 
 renameBundleExternalKey(prefix)  ::= <<
-update bundles b
-join (select
-     record_id
-     , external_key
-     from
-     bundles
-     where external_key = :externalKey <AND_CHECK_TENANT("")>) t
-on b.record_id = t.record_id
-set b.external_key = concat('kb', '<prefix>', '-', t.record_id, ':', t.external_key)
+update bundles
+set external_key = concat('kb', '<prefix>', '-', record_id, ':', external_key)
+where external_key = :externalKey
+<AND_CHECK_TENANT("")>
 ;
 >>
 
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java
index 4ed3a8f..8174045 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java
@@ -24,6 +24,7 @@ import org.joda.time.DateTime;
 import org.joda.time.Interval;
 import org.joda.time.LocalDate;
 import org.killbill.billing.ErrorCode;
+import org.killbill.billing.api.FlakyRetryAnalyzer;
 import org.killbill.billing.api.TestApiListener.NextEvent;
 import org.killbill.billing.catalog.api.BillingActionPolicy;
 import org.killbill.billing.catalog.api.BillingPeriod;
@@ -205,7 +206,8 @@ public class TestUserApiCancel extends SubscriptionTestSuiteWithEmbeddedDB {
 
     // Similar test to testCancelSubscriptionEOTWithChargeThroughDate except we uncancel and check things
     // are as they used to be and we can move forward without hitting cancellation
-    @Test(groups = "slow")
+    // Flaky, see https://github.com/killbill/killbill/issues/860
+    @Test(groups = "slow", retryAnalyzer = FlakyRetryAnalyzer.class)
     public void testUncancel() throws SubscriptionBillingApiException, SubscriptionBaseApiException {
         final String prod = "Shotgun";
         final BillingPeriod term = BillingPeriod.MONTHLY;
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java
index 143041e..ba1229a 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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:
  *
@@ -16,6 +18,7 @@
 
 package org.killbill.billing.subscription.api.user;
 
+import java.sql.SQLException;
 import java.sql.SQLIntegrityConstraintViolationException;
 import java.util.List;
 
@@ -34,7 +37,6 @@ import org.killbill.billing.subscription.DefaultSubscriptionTestInitializer;
 import org.killbill.billing.subscription.SubscriptionTestSuiteWithEmbeddedDB;
 import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
 import org.killbill.billing.subscription.events.phase.PhaseEvent;
-import org.mariadb.jdbc.internal.util.dao.QueryException;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
@@ -82,7 +84,7 @@ public class TestUserApiCreate extends SubscriptionTestSuiteWithEmbeddedDB {
             subscriptionInternalApi.createBundleForAccount(bundle.getAccountId(), DefaultSubscriptionTestInitializer.DEFAULT_BUNDLE_KEY, false, internalCallContext);
             Assert.fail("createBundleForAccount should fail because key already exists");
         } catch (final RuntimeException e) {
-            assertTrue(e.getCause() instanceof SQLIntegrityConstraintViolationException);
+            assertTrue(e.getCause() instanceof SQLException && (e.getCause() instanceof SQLIntegrityConstraintViolationException || "23505".compareTo(((SQLException) e.getCause()).getSQLState()) == 0));
         }
 
         final SubscriptionBaseBundle newBundle = subscriptionInternalApi.createBundleForAccount(bundle.getAccountId(), DefaultSubscriptionTestInitializer.DEFAULT_BUNDLE_KEY, true, internalCallContext);
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java
index 04f0ab5..79bb057 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java
@@ -25,6 +25,7 @@ import javax.annotation.Nullable;
 import org.joda.time.DateTime;
 import org.joda.time.Interval;
 import org.killbill.billing.ErrorCode;
+import org.killbill.billing.api.FlakyRetryAnalyzer;
 import org.killbill.billing.api.TestApiListener.NextEvent;
 import org.killbill.billing.catalog.api.BillingActionPolicy;
 import org.killbill.billing.catalog.api.BillingPeriod;
@@ -106,7 +107,8 @@ public class TestUserApiError extends SubscriptionTestSuiteNoDB {
         }
     }
 
-    @Test(groups = "fast")
+    // Flaky, see https://github.com/killbill/killbill/issues/860
+    @Test(groups = "fast", retryAnalyzer = FlakyRetryAnalyzer.class)
     public void testChangeSubscriptionNonActive() throws SubscriptionBaseApiException {
         final SubscriptionBase subscription = testUtil.createSubscription(bundle, "Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME);
 
diff --git a/util/src/main/resources/trimTenant.sql b/util/src/main/resources/trimTenant.sql
new file mode 100644
index 0000000..d0bfb0c
--- /dev/null
+++ b/util/src/main/resources/trimTenant.sql
@@ -0,0 +1,75 @@
+drop procedure if exists trimTenant;
+DELIMITER //
+CREATE PROCEDURE trimTenant(p_api_key varchar(36))
+BEGIN
+
+    DECLARE v_tenant_record_id bigint /*! unsigned */;
+
+    select record_id from tenants WHERE api_key = p_api_key into v_tenant_record_id;
+
+    DELETE FROM analytics_account_fields WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM analytics_account_tags WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM analytics_account_transitions WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM analytics_accounts WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM analytics_bundle_fields WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM analytics_bundle_tags WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM analytics_bundles WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM analytics_invoice_adjustments WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM analytics_invoice_credits WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM analytics_invoice_fields WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM analytics_invoice_item_adjustments WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM analytics_invoice_items WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM analytics_invoice_payment_fields WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM analytics_invoice_tags WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM analytics_invoices WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM analytics_notifications WHERE search_key2 = v_tenant_record_id;
+    DELETE FROM analytics_notifications_history WHERE search_key2 = v_tenant_record_id;
+    DELETE FROM analytics_payment_auths WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM analytics_payment_captures WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM analytics_payment_chargebacks WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM analytics_payment_credits WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM analytics_payment_fields WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM analytics_payment_method_fields WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM analytics_payment_purchases WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM analytics_payment_refunds WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM analytics_payment_tags WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM analytics_payment_voids WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM analytics_subscription_transitions WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM analytics_transaction_fields WHERE tenant_record_id = v_tenant_record_id;
+
+    DELETE FROM account_email_history WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM account_emails WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM account_history WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM accounts WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM audit_log WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM blocking_states WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM bundles WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM bus_events WHERE search_key2 = v_tenant_record_id;
+    DELETE FROM bus_events_history WHERE search_key2 = v_tenant_record_id;
+    DELETE FROM bus_ext_events WHERE search_key2 = v_tenant_record_id;
+    DELETE FROM bus_ext_events_history WHERE search_key2 = v_tenant_record_id;
+    DELETE FROM custom_field_history WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM custom_fields WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM invoice_items WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM invoice_parent_children WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM invoice_payments WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM invoices WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM notifications WHERE search_key2 = v_tenant_record_id;
+    DELETE FROM notifications_history WHERE search_key2 = v_tenant_record_id;
+    DELETE FROM payment_attempt_history WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM payment_attempts WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM payment_history WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM payment_method_history WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM payment_methods WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM payment_transaction_history WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM payment_transactions WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM payments WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM rolled_up_usage WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM subscription_events WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM subscriptions WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM tag_history WHERE tenant_record_id = v_tenant_record_id;
+    DELETE FROM tags WHERE tenant_record_id = v_tenant_record_id;
+
+    END;
+//
+DELIMITER ;
diff --git a/util/src/test/java/org/killbill/billing/DBTestingHelper.java b/util/src/test/java/org/killbill/billing/DBTestingHelper.java
index 2808866..d940406 100644
--- a/util/src/test/java/org/killbill/billing/DBTestingHelper.java
+++ b/util/src/test/java/org/killbill/billing/DBTestingHelper.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -19,18 +19,27 @@
 package org.killbill.billing;
 
 import java.io.IOException;
+import java.io.PrintWriter;
 import java.net.URL;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.SQLNonTransientConnectionException;
 import java.util.Enumeration;
-import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.sql.DataSource;
 
 import org.killbill.billing.platform.test.PlatformDBTestingHelper;
 import org.killbill.billing.util.glue.IDBISetup;
 import org.killbill.billing.util.io.IOUtils;
 import org.killbill.commons.embeddeddb.EmbeddedDB;
+import org.killbill.commons.jdbi.guice.DBIProvider;
 import org.skife.jdbi.v2.DBI;
 import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.ResultSetMapperFactory;
 import org.skife.jdbi.v2.tweak.ResultSetMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.base.MoreObjects;
 
@@ -38,7 +47,7 @@ public class DBTestingHelper extends PlatformDBTestingHelper {
 
     private static DBTestingHelper dbTestingHelper = null;
 
-    private AtomicBoolean initialized;
+    private DBI dbi;
 
     public static synchronized DBTestingHelper get() {
         if (dbTestingHelper == null) {
@@ -49,18 +58,18 @@ public class DBTestingHelper extends PlatformDBTestingHelper {
 
     private DBTestingHelper() {
         super();
-        initialized = new AtomicBoolean(false);
     }
 
     @Override
-    public IDBI getDBI() {
-        final DBI dbi = (DBI) super.getDBI();
-        // Register KB specific mappers
-        if (initialized.compareAndSet(false, true)) {
+    public synchronized IDBI getDBI() {
+        if (dbi == null) {
+            final RetryableDataSource retryableDataSource = new RetryableDataSource(getDataSource());
+            dbi = (DBI) new DBIProvider(null, retryableDataSource, null).get();
+
+            // Register KB specific mappers
             for (final ResultSetMapperFactory resultSetMapperFactory : IDBISetup.mapperFactoriesToRegister()) {
                 dbi.registerMapper(resultSetMapperFactory);
             }
-
             for (final ResultSetMapper resultSetMapper : IDBISetup.mappersToRegister()) {
                 dbi.registerMapper(resultSetMapper);
             }
@@ -202,4 +211,73 @@ public class DBTestingHelper extends PlatformDBTestingHelper {
             }
         }
     }
+
+    // DataSource which will retry recreating a connection once in case of a connection exception.
+    // This is useful for transient network errors in tests when using a separate database (e.g. Docker container),
+    // as we don't use a connection pool.
+    private static final class RetryableDataSource implements DataSource {
+
+        private static final Logger logger = LoggerFactory.getLogger(RetryableDataSource.class);
+
+        private final DataSource delegate;
+
+        private RetryableDataSource(final DataSource delegate) {
+            this.delegate = delegate;
+        }
+
+        @Override
+        public Connection getConnection() throws SQLException {
+            try {
+                return delegate.getConnection();
+            } catch (final SQLNonTransientConnectionException e) {
+                logger.warn("Unable to retrieve connection, attempting to retry", e);
+                return delegate.getConnection();
+            }
+        }
+
+        @Override
+        public Connection getConnection(final String username, final String password) throws SQLException {
+            try {
+                return delegate.getConnection(username, password);
+            } catch (final SQLNonTransientConnectionException e) {
+                logger.warn("Unable to retrieve connection, attempting to retry", e);
+                return delegate.getConnection(username, password);
+            }
+        }
+
+        @Override
+        public <T> T unwrap(final Class<T> iface) throws SQLException {
+            return delegate.unwrap(iface);
+        }
+
+        @Override
+        public boolean isWrapperFor(final Class<?> iface) throws SQLException {
+            return delegate.isWrapperFor(iface);
+        }
+
+        @Override
+        public PrintWriter getLogWriter() throws SQLException {
+            return delegate.getLogWriter();
+        }
+
+        @Override
+        public void setLogWriter(final PrintWriter out) throws SQLException {
+            delegate.setLogWriter(out);
+        }
+
+        @Override
+        public void setLoginTimeout(final int seconds) throws SQLException {
+            delegate.setLoginTimeout(seconds);
+        }
+
+        @Override
+        public int getLoginTimeout() throws SQLException {
+            return delegate.getLoginTimeout();
+        }
+
+        //@Override
+        public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException {
+            throw new SQLFeatureNotSupportedException("javax.sql.DataSource.getParentLogger() is not currently supported by " + this.getClass().getName());
+        }
+    }
 }