killbill-memoizeit

Changes

Details

diff --git a/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceFormatter.java b/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceFormatter.java
index 9ef4144..7a063cc 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceFormatter.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceFormatter.java
@@ -19,5 +19,12 @@ package com.ning.billing.invoice.api.formatters;
 import com.ning.billing.invoice.api.Invoice;
 
 public interface InvoiceFormatter extends Invoice {
+
     public String getFormattedInvoiceDate();
+
+    public String getFormattedChargedAmount();
+
+    public String getFormattedPaidAmount();
+
+    public String getFormattedBalance();
 }
diff --git a/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceItemFormatter.java b/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceItemFormatter.java
index 7175fde..2927744 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceItemFormatter.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceItemFormatter.java
@@ -19,7 +19,10 @@ package com.ning.billing.invoice.api.formatters;
 import com.ning.billing.invoice.api.InvoiceItem;
 
 public interface InvoiceItemFormatter extends InvoiceItem {
+
     public String getFormattedStartDate();
 
     public String getFormattedEndDate();
+
+    public String getFormattedAmount();
 }
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java
index fa58913..1d63e9d 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java
@@ -57,6 +57,14 @@ public interface InvoiceUserApi {
     public BigDecimal getAccountBalance(UUID accountId);
 
     /**
+     * Retrieve the account CBA.
+     *
+     * @param accountId account id
+     * @return the account CBA
+     */
+    public BigDecimal getAccountCBA(UUID accountId);
+
+    /**
      * Retrieve an invoice by id.
      *
      * @param invoiceId invoice id
@@ -260,6 +268,17 @@ public interface InvoiceUserApi {
                                                    BigDecimal amount, Currency currency, CallContext context) throws InvoiceApiException;
 
     /**
+     * Delete a CBA item.
+     *
+     * @param accountId     account id
+     * @param invoiceId     invoice id
+     * @param invoiceItemId invoice item id (must be of type CBA_ADJ)
+     * @param context       the call context
+     * @throws InvoiceApiException
+     */
+    public void deleteCBA(UUID accountId, UUID invoiceId, UUID invoiceItemId, CallContext context) throws InvoiceApiException;
+
+    /**
      * Retrieve the invoice formatted in HTML.
      *
      * @param invoiceId invoice id
diff --git a/api/src/main/java/com/ning/billing/overdue/OverdueCancellationPolicicy.java b/api/src/main/java/com/ning/billing/overdue/OverdueCancellationPolicicy.java
index dda0dd6..850dcf7 100644
--- a/api/src/main/java/com/ning/billing/overdue/OverdueCancellationPolicicy.java
+++ b/api/src/main/java/com/ning/billing/overdue/OverdueCancellationPolicicy.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2010-2012 Ning, Inc.
  *
  * Ning licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueBase.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueBase.java
index 773a1eb..27de0ba 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueBase.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueBase.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2010-2012 Ning, Inc.
  *
  * Ning licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueWithSubscriptionCancellation.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueWithSubscriptionCancellation.java
index 618162b..11c5e8d 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueWithSubscriptionCancellation.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueWithSubscriptionCancellation.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2010-2012 Ning, Inc.
  *
  * Ning licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
index 11efa03..6e05ad7 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
@@ -27,7 +27,6 @@ import javax.annotation.Nullable;
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
 
-import com.google.inject.Inject;
 import com.ning.billing.ErrorCode;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountApiException;
@@ -50,6 +49,8 @@ import com.ning.billing.util.dao.ObjectType;
 import com.ning.billing.util.tag.ControlTagType;
 import com.ning.billing.util.tag.Tag;
 
+import com.google.inject.Inject;
+
 public class DefaultInvoiceUserApi implements InvoiceUserApi {
 
     private final InvoiceDao dao;
@@ -90,6 +91,12 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
     }
 
     @Override
+    public BigDecimal getAccountCBA(final UUID accountId) {
+        final BigDecimal result = dao.getAccountCBA(accountId);
+        return result == null ? BigDecimal.ZERO : result;
+    }
+
+    @Override
     public Invoice getInvoice(final UUID invoiceId) throws InvoiceApiException {
         return dao.getById(invoiceId);
     }
@@ -219,6 +226,11 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
     }
 
     @Override
+    public void deleteCBA(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId, final CallContext context) throws InvoiceApiException {
+        dao.deleteCBA(accountId, invoiceId, invoiceItemId, context);
+    }
+
+    @Override
     public String getInvoiceAsHTML(final UUID invoiceId) throws AccountApiException, IOException, InvoiceApiException {
         final Invoice invoice = getInvoice(invoiceId);
         if (invoice == null) {
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/AuditedInvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/AuditedInvoiceDao.java
index 923bd25..08db235 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/AuditedInvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/AuditedInvoiceDao.java
@@ -35,12 +35,6 @@ import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Objects;
-import com.google.common.base.Predicate;
-import com.google.common.collect.Collections2;
-import com.google.common.collect.ImmutableMap.Builder;
-import com.google.inject.Inject;
 import com.ning.billing.ErrorCode;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.Invoice;
@@ -72,6 +66,14 @@ import com.ning.billing.util.dao.ObjectType;
 import com.ning.billing.util.dao.TableName;
 import com.ning.billing.util.tag.ControlTagType;
 
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Objects;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableMap.Builder;
+import com.google.common.collect.Lists;
+import com.google.inject.Inject;
+
 public class AuditedInvoiceDao implements InvoiceDao {
 
     private static final Logger log = LoggerFactory.getLogger(AuditedInvoiceDao.class);
@@ -579,7 +581,7 @@ public class AuditedInvoiceDao implements InvoiceDao {
     @Override
     public InvoiceItem insertExternalCharge(final UUID accountId, @Nullable final UUID invoiceId, @Nullable final UUID bundleId, final String description,
                                             final BigDecimal amount, final LocalDate effectiveDate, final Currency currency, final CallContext context)
-                                                    throws InvoiceApiException {
+            throws InvoiceApiException {
         return invoiceSqlDao.inTransaction(new Transaction<InvoiceItem, InvoiceSqlDao>() {
             @Override
             public InvoiceItem inTransaction(final InvoiceSqlDao transactional, final TransactionStatus status) throws Exception {
@@ -673,6 +675,90 @@ public class AuditedInvoiceDao implements InvoiceDao {
     }
 
     @Override
+    public void deleteCBA(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId, final CallContext context) throws InvoiceApiException {
+        invoiceSqlDao.inTransaction(new Transaction<Void, InvoiceSqlDao>() {
+            @Override
+            public Void inTransaction(final InvoiceSqlDao transactional, final TransactionStatus status) throws Exception {
+                // Retrieve the invoice and make sure it belongs to the right account
+                final Invoice invoice = transactional.getById(invoiceId.toString());
+                if (invoice == null || !invoice.getAccountId().equals(accountId)) {
+                    throw new InvoiceApiException(ErrorCode.INVOICE_NOT_FOUND, invoiceId);
+                }
+
+                // Retrieve the invoice item and make sure it belongs to the right invoice
+                final InvoiceItemSqlDao invoiceItemSqlDao = transactional.become(InvoiceItemSqlDao.class);
+                final InvoiceItem cbaItem = invoiceItemSqlDao.getById(invoiceItemId.toString());
+                if (cbaItem == null || !cbaItem.getInvoiceId().equals(invoice.getId())) {
+                    throw new InvoiceApiException(ErrorCode.INVOICE_ITEM_NOT_FOUND, invoiceItemId);
+                }
+
+                // First, adjust the same invoice with the CBA amount to "delete"
+                final InvoiceItem cbaAdjItem = new CreditBalanceAdjInvoiceItem(invoice.getId(), invoice.getAccountId(), context.getCreatedDate().toLocalDate(),
+                                                                               cbaItem.getId(), cbaItem.getAmount().negate(), cbaItem.getCurrency());
+                invoiceItemSqlDao.create(cbaAdjItem, context);
+
+                // If there is more account credit than CBA we adjusted, we're done.
+                // Otherwise, we need to find further invoices on which this credit was consumed
+                final BigDecimal accountCBA = getAccountCBAFromTransaction(accountId, invoiceSqlDao);
+                if (accountCBA.compareTo(BigDecimal.ZERO) < 0) {
+                    if (accountCBA.compareTo(cbaItem.getAmount().negate()) < 0) {
+                        throw new IllegalStateException("The account balance can't be lower than the amount adjusted");
+                    }
+                    final List<Invoice> invoicesFollowing = transactional.getInvoicesByAccountAfterDate(accountId.toString(),
+                                                                                                        invoice.getInvoiceDate().toDateTimeAtStartOfDay().toDate());
+                    populateChildren(invoicesFollowing, transactional);
+
+                    // The remaining amount to adjust (i.e. the amount of credits used on following invoices)
+                    // is the current account CBA balance (minus the sign)
+                    BigDecimal positiveRemainderToAdjust = accountCBA.negate();
+                    for (final Invoice invoiceFollowing : invoicesFollowing) {
+                        if (invoiceFollowing.getId().equals(invoice.getId())) {
+                            continue;
+                        }
+
+                        // Add a single adjustment per invoice
+                        BigDecimal positiveCBAAdjItemAmount = BigDecimal.ZERO;
+
+                        // Start with the most recent invoice to limit the impact on overdue
+                        for (final InvoiceItem cbaUsed : Lists.reverse(invoiceFollowing.getInvoiceItems())) {
+                            // Ignore non CBA items or credits (CBA >= 0)
+                            if (!InvoiceItemType.CBA_ADJ.equals(cbaUsed.getInvoiceItemType()) ||
+                                cbaUsed.getAmount().compareTo(BigDecimal.ZERO) >= 0) {
+                                continue;
+                            }
+
+                            final BigDecimal positiveCBAUsedAmount = cbaUsed.getAmount().negate();
+                            final BigDecimal positiveNextCBAAdjItemAmount;
+                            if (positiveCBAUsedAmount.compareTo(positiveRemainderToAdjust) < 0) {
+                                positiveNextCBAAdjItemAmount = positiveCBAUsedAmount;
+                                positiveRemainderToAdjust = positiveRemainderToAdjust.min(positiveNextCBAAdjItemAmount);
+                            } else {
+                                positiveNextCBAAdjItemAmount = positiveRemainderToAdjust;
+                                positiveRemainderToAdjust = BigDecimal.ZERO;
+                            }
+                            positiveCBAAdjItemAmount = positiveCBAAdjItemAmount.add(positiveNextCBAAdjItemAmount);
+
+                            if (positiveRemainderToAdjust.compareTo(BigDecimal.ZERO) == 0) {
+                                break;
+                            }
+                        }
+
+                        // Add the adjustment on that invoice
+                        final InvoiceItem nextCBAAdjItem = new CreditBalanceAdjInvoiceItem(invoiceFollowing.getId(), invoice.getAccountId(), context.getCreatedDate().toLocalDate(),
+                                                                                           cbaItem.getId(), positiveCBAAdjItemAmount, cbaItem.getCurrency());
+                        invoiceItemSqlDao.create(nextCBAAdjItem, context);
+                        if (positiveRemainderToAdjust.compareTo(BigDecimal.ZERO) == 0) {
+                            break;
+                        }
+                    }
+                }
+
+                return null;
+            }
+        });
+    }
+
+    @Override
     public void test() {
         invoiceSqlDao.test();
     }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
index 207c1de..936fc83 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
@@ -158,4 +158,13 @@ public interface InvoiceDao {
      */
     InvoiceItem insertInvoiceItemAdjustment(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId, final LocalDate effectiveDate,
                                             @Nullable final BigDecimal amount, @Nullable final Currency currency, final CallContext context);
+
+    /**
+     * Delete a CBA item.
+     *
+     * @param accountId     the account id
+     * @param invoiceId     the invoice id
+     * @param invoiceItemId the invoice item id of the cba item to delete
+     */
+    void deleteCBA(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId, final CallContext context) throws InvoiceApiException;
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
index 2391bc9..b990922 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
@@ -143,7 +143,7 @@ public interface InvoiceItemSqlDao extends EntitySqlDao<InvoiceItem> {
                     item = new RecurringInvoiceItem(id, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, rate, currency);
                     break;
                 case CBA_ADJ:
-                    item = new CreditBalanceAdjInvoiceItem(id, invoiceId, accountId, startDate, amount, currency);
+                    item = new CreditBalanceAdjInvoiceItem(id, invoiceId, accountId, startDate, linkedItemId, amount, currency);
                     break;
                 case CREDIT_ADJ:
                     item = new CreditAdjInvoiceItem(id, invoiceId, accountId, startDate, amount, currency);
diff --git a/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
index c1e3140..c07f13b 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
@@ -57,7 +57,6 @@ import com.ning.billing.invoice.model.RepairAdjInvoiceItem;
 import com.ning.billing.junction.api.BillingEventSet;
 import com.ning.billing.util.clock.Clock;
 
-import com.google.common.base.Function;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
 import com.google.inject.Inject;
@@ -82,9 +81,9 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
      */
     @Override
     public Invoice generateInvoice(final UUID accountId, @Nullable final BillingEventSet events,
-            @Nullable final List<Invoice> existingInvoices,
-            final LocalDate targetDate, final DateTimeZone accountTimeZone,
-            final Currency targetCurrency) throws InvoiceApiException {
+                                   @Nullable final List<Invoice> existingInvoices,
+                                   final LocalDate targetDate, final DateTimeZone accountTimeZone,
+                                   final Currency targetCurrency) throws InvoiceApiException {
         if ((events == null) || (events.size() == 0) || events.isAccountAutoInvoiceOff()) {
             return null;
         }
@@ -98,8 +97,8 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
             for (final Invoice invoice : existingInvoices) {
                 for (final InvoiceItem item : invoice.getInvoiceItems()) {
                     if (item.getSubscriptionId() == null || // Always include migration invoices, credits, external charges etc.
-                            !events.getSubscriptionIdsWithAutoInvoiceOff()
-                            .contains(item.getSubscriptionId())) { //don't add items with auto_invoice_off tag
+                        !events.getSubscriptionIdsWithAutoInvoiceOff()
+                               .contains(item.getSubscriptionId())) { //don't add items with auto_invoice_off tag
                         existingItems.add(item);
                     }
                 }
@@ -131,7 +130,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
     }
 
     void generateCBAForExistingInvoices(final UUID accountId, final List<Invoice> existingInvoices,
-            final List<InvoiceItem> proposedItems, final Currency currency) {
+                                        final List<InvoiceItem> proposedItems, final Currency currency) {
         // Determine most accurate invoice balances up to this point
         final Map<UUID, BigDecimal> amountOwedByInvoice = new HashMap<UUID, BigDecimal>();
 
@@ -163,7 +162,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
     void addRepairedItems(final List<InvoiceItem> existingItems, final List<InvoiceItem> proposedItems) {
         for (final InvoiceItem existingItem : existingItems) {
             if (existingItem.getInvoiceItemType() == InvoiceItemType.RECURRING ||
-                    existingItem.getInvoiceItemType() == InvoiceItemType.FIXED) {
+                existingItem.getInvoiceItemType() == InvoiceItemType.FIXED) {
                 final BigDecimal existingAdjustedPositiveAmount = getAdjustedPositiveAmount(existingItems, existingItem.getId());
                 final BigDecimal amountNegated = existingItem.getAmount() == null ? null : existingItem.getAmount().subtract(existingAdjustedPositiveAmount).negate();
                 if (amountNegated.compareTo(BigDecimal.ZERO) < 0) {
@@ -175,7 +174,6 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
 
     }
 
-
     // We check to see if there are any adjustments that point to the item we are trying to repair
     // If we did any CREDIT_ADJ or REFUND_ADJ, then we unfortunately we can't know what is the intent
     // was as it applies to the full Invoice, so we ignore it. That might result in an extra positive CBA
@@ -188,7 +186,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
             @Override
             public boolean apply(InvoiceItem item) {
                 if (item.getInvoiceItemType() == InvoiceItemType.ITEM_ADJ &&
-                        item.getLinkedItemId() != null && item.getLinkedItemId().equals(linkedItemId)) {
+                    item.getLinkedItemId() != null && item.getLinkedItemId().equals(linkedItemId)) {
                     return true;
                 } else {
                     return false;
@@ -204,7 +202,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
     }
 
     void consumeExistingCredit(final UUID invoiceId, final UUID accountId, final List<InvoiceItem> existingItems,
-            final List<InvoiceItem> proposedItems, final Currency targetCurrency) {
+                               final List<InvoiceItem> proposedItems, final Currency targetCurrency) {
         BigDecimal totalUnusedCreditAmount = BigDecimal.ZERO;
         BigDecimal totalAmountOwed = BigDecimal.ZERO;
 
@@ -266,7 +264,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
      * Removes all matching items from both submitted collections
      */
     void removeDuplicatedInvoiceItems(final List<InvoiceItem> proposedItems,
-            final List<InvoiceItem> existingInvoiceItems) {
+                                      final List<InvoiceItem> existingInvoiceItems) {
         // We can't just use sets here as order matters (we want to keep duplicated in existingInvoiceItems)
         final Iterator<InvoiceItem> proposedItemIterator = proposedItems.iterator();
         while (proposedItemIterator.hasNext()) {
@@ -305,7 +303,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
     }
 
     private List<InvoiceItem> generateInvoiceItems(final UUID invoiceId, final UUID accountId, final BillingEventSet events,
-            final LocalDate targetDate, final DateTimeZone accountTimeZone, final Currency currency) throws InvoiceApiException {
+                                                   final LocalDate targetDate, final DateTimeZone accountTimeZone, final Currency currency) throws InvoiceApiException {
         final List<InvoiceItem> items = new ArrayList<InvoiceItem>();
 
         if (events.size() == 0) {
@@ -343,7 +341,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
 
     // Turn a set of events into a list of invoice items. Note that the dates on the invoice items will be rounded (granularity of a day)
     private List<InvoiceItem> processEvents(final UUID invoiceId, final UUID accountId, final BillingEvent thisEvent, @Nullable final BillingEvent nextEvent,
-            final LocalDate targetDate, final DateTimeZone accountTimeZone, final Currency currency) throws InvoiceApiException {
+                                            final LocalDate targetDate, final DateTimeZone accountTimeZone, final Currency currency) throws InvoiceApiException {
         final List<InvoiceItem> items = new ArrayList<InvoiceItem>();
 
         // Handle fixed price items
@@ -377,13 +375,13 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
                         final BigDecimal amount = itemDatum.getNumberOfCycles().multiply(rate).setScale(NUMBER_OF_DECIMALS, ROUNDING_MODE);
 
                         final RecurringInvoiceItem recurringItem = new RecurringInvoiceItem(invoiceId,
-                                accountId,
-                                thisEvent.getSubscription().getBundleId(),
-                                thisEvent.getSubscription().getId(),
-                                thisEvent.getPlan().getName(),
-                                thisEvent.getPlanPhase().getName(),
-                                itemDatum.getStartDate(), itemDatum.getEndDate(),
-                                amount, rate, currency);
+                                                                                            accountId,
+                                                                                            thisEvent.getSubscription().getBundleId(),
+                                                                                            thisEvent.getSubscription().getId(),
+                                                                                            thisEvent.getPlan().getName(),
+                                                                                            thisEvent.getPlanPhase().getName(),
+                                                                                            itemDatum.getStartDate(), itemDatum.getEndDate(),
+                                                                                            amount, rate, currency);
                         items.add(recurringItem);
                     }
                 }
@@ -396,15 +394,15 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
 
     private BillingMode instantiateBillingMode(final BillingModeType billingMode) {
         switch (billingMode) {
-        case IN_ADVANCE:
-            return new InAdvanceBillingMode();
-        default:
-            throw new UnsupportedOperationException();
+            case IN_ADVANCE:
+                return new InAdvanceBillingMode();
+            default:
+                throw new UnsupportedOperationException();
         }
     }
 
     InvoiceItem generateFixedPriceItem(final UUID invoiceId, final UUID accountId, final BillingEvent thisEvent,
-            final LocalDate targetDate, final Currency currency) {
+                                       final LocalDate targetDate, final Currency currency) {
         final LocalDate roundedStartDate = new LocalDate(thisEvent.getEffectiveDate(), thisEvent.getTimeZone());
 
         if (roundedStartDate.isAfter(targetDate)) {
@@ -414,9 +412,9 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
 
             if (fixedPrice != null) {
                 return new FixedPriceInvoiceItem(invoiceId, accountId, thisEvent.getSubscription().getBundleId(),
-                        thisEvent.getSubscription().getId(),
-                        thisEvent.getPlan().getName(), thisEvent.getPlanPhase().getName(),
-                        roundedStartDate, fixedPrice, currency);
+                                                 thisEvent.getSubscription().getId(),
+                                                 thisEvent.getPlan().getName(), thisEvent.getPlanPhase().getName(),
+                                                 roundedStartDate, fixedPrice, currency);
             } else {
                 return null;
             }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/CreditAdjInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/CreditAdjInvoiceItem.java
index 6c856e8..3b43340 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/CreditAdjInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/CreditAdjInvoiceItem.java
@@ -19,13 +19,13 @@ package com.ning.billing.invoice.model;
 import java.math.BigDecimal;
 import java.util.UUID;
 
-import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
 
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.InvoiceItemType;
 
 public class CreditAdjInvoiceItem extends AdjInvoiceItem {
+
     public CreditAdjInvoiceItem(final UUID invoiceId, final UUID accountId, final LocalDate date,
                                 final BigDecimal amount, final Currency currency) {
         super(invoiceId, accountId, date, date, amount, currency);
@@ -43,6 +43,6 @@ public class CreditAdjInvoiceItem extends AdjInvoiceItem {
 
     @Override
     public String getDescription() {
-        return "credit-adj";
+        return "Invoice adjustment";
     }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/CreditBalanceAdjInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/CreditBalanceAdjInvoiceItem.java
index 3eb05b2..588a650 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/CreditBalanceAdjInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/CreditBalanceAdjInvoiceItem.java
@@ -25,14 +25,21 @@ import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.InvoiceItemType;
 
 public class CreditBalanceAdjInvoiceItem extends AdjInvoiceItem {
+
     public CreditBalanceAdjInvoiceItem(final UUID invoiceId, final UUID accountId,
                                        final LocalDate date, final BigDecimal amount, final Currency currency) {
         super(invoiceId, accountId, date, date, amount, currency);
     }
 
     public CreditBalanceAdjInvoiceItem(final UUID id, final UUID invoiceId, final UUID accountId,
-                                       final LocalDate date, final BigDecimal amount, final Currency currency) {
-        super(id, invoiceId, accountId, date, date, amount, currency);
+                                       final LocalDate date, final UUID linkedInvoiceItemId,
+                                       final BigDecimal amount, final Currency currency) {
+        super(id, invoiceId, accountId, date, date, amount, currency, linkedInvoiceItemId);
+    }
+
+    public CreditBalanceAdjInvoiceItem(final UUID invoiceId, final UUID accountId, final LocalDate date, final UUID linkedInvoiceItemId,
+                                       final BigDecimal amount, final Currency currency) {
+        super(invoiceId, accountId, date, date, amount, currency, linkedInvoiceItemId);
     }
 
     @Override
@@ -42,6 +49,12 @@ public class CreditBalanceAdjInvoiceItem extends AdjInvoiceItem {
 
     @Override
     public String getDescription() {
-        return "cba-adj";
+        final String secondDescription;
+        if (getAmount().compareTo(BigDecimal.ZERO) >= 0) {
+            secondDescription = "account credit";
+        } else {
+            secondDescription = "use of account credit";
+        }
+        return String.format("Adjustment (%s)", secondDescription);
     }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/ExternalChargeInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/ExternalChargeInvoiceItem.java
index c8f0245..44c915e 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/ExternalChargeInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/ExternalChargeInvoiceItem.java
@@ -41,7 +41,11 @@ public class ExternalChargeInvoiceItem extends InvoiceItemBase {
 
     @Override
     public String getDescription() {
-        return String.format("%s (external charge) on %s", getPlanName(), getStartDate().toString());
+        if (getPlanName() == null) {
+            return "External charge";
+        } else {
+            return String.format("%s (external charge)", getPlanName());
+        }
     }
 
     @Override
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
index f2d641e..d169d78 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
@@ -28,6 +28,7 @@ import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.invoice.api.InvoiceItemType;
 
 public class FixedPriceInvoiceItem extends InvoiceItemBase {
+
     public FixedPriceInvoiceItem(final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId, @Nullable final UUID subscriptionId, final String planName, final String phaseName,
                                  final LocalDate date, final BigDecimal amount, final Currency currency) {
         super(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, date, null, amount, currency);
@@ -40,7 +41,15 @@ public class FixedPriceInvoiceItem extends InvoiceItemBase {
 
     @Override
     public String getDescription() {
-        return String.format("%s (fixed price) on %s", getPhaseName(), getStartDate().toString());
+        if (getPhaseName() == null) {
+            return "Fixed price charge";
+        } else {
+            if (getAmount().compareTo(BigDecimal.ZERO) == 0) {
+                return getPhaseName();
+            } else {
+                return String.format("%s (fixed price)", getPhaseName());
+            }
+        }
     }
 
     @Override
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/ItemAdjInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/ItemAdjInvoiceItem.java
index cbfe229..5392adf 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/ItemAdjInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/ItemAdjInvoiceItem.java
@@ -45,6 +45,6 @@ public class ItemAdjInvoiceItem extends AdjInvoiceItem {
 
     @Override
     public String getDescription() {
-        return "item-adj";
+        return "Invoice item adjustment";
     }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java
index d6f98a2..c3f4afa 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java
@@ -50,7 +50,7 @@ public class RecurringInvoiceItem extends InvoiceItemBase {
 
     @Override
     public String getDescription() {
-        return String.format("%s from %s to %s", phaseName, startDate.toString(dateTimeFormatter), endDate.toString(dateTimeFormatter));
+        return phaseName;
     }
 
     @Override
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/RefundAdjInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/RefundAdjInvoiceItem.java
index 9091b6c..6ccef1e 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/RefundAdjInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/RefundAdjInvoiceItem.java
@@ -25,6 +25,7 @@ import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.InvoiceItemType;
 
 public class RefundAdjInvoiceItem extends AdjInvoiceItem {
+
     public RefundAdjInvoiceItem(final UUID invoiceId, final UUID accountId, final LocalDate date,
                                 final BigDecimal amount, final Currency currency) {
         super(invoiceId, accountId, date, date, amount, currency);
@@ -42,6 +43,6 @@ public class RefundAdjInvoiceItem extends AdjInvoiceItem {
 
     @Override
     public String getDescription() {
-        return "refund-adj";
+        return "Invoice adjustment";
     }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/RepairAdjInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/RepairAdjInvoiceItem.java
index 36327d2..1b3155b 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/RepairAdjInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/RepairAdjInvoiceItem.java
@@ -25,6 +25,7 @@ import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.InvoiceItemType;
 
 public class RepairAdjInvoiceItem extends AdjInvoiceItem {
+
     public RepairAdjInvoiceItem(final UUID invoiceId, final UUID accountId, final LocalDate startDate, final LocalDate endDate,
                                 final BigDecimal amount, final Currency currency, final UUID reversingId) {
         super(invoiceId, accountId, startDate, endDate, amount, currency, reversingId);
@@ -42,6 +43,6 @@ public class RepairAdjInvoiceItem extends AdjInvoiceItem {
 
     @Override
     public String getDescription() {
-        return "repair-adj";
+        return "Adjustment (entitlement change)";
     }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
index f9ef212..b6edb8d 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
@@ -33,6 +33,7 @@
 package com.ning.billing.invoice.template.formatters;
 
 import java.math.BigDecimal;
+import java.text.NumberFormat;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
@@ -45,8 +46,11 @@ import org.joda.time.format.DateTimeFormatter;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceItemType;
 import com.ning.billing.invoice.api.InvoicePayment;
 import com.ning.billing.invoice.api.formatters.InvoiceFormatter;
+import com.ning.billing.invoice.model.CreditAdjInvoiceItem;
+import com.ning.billing.invoice.model.CreditBalanceAdjInvoiceItem;
 import com.ning.billing.util.template.translation.TranslatorConfig;
 
 import com.google.common.base.Objects;
@@ -56,7 +60,7 @@ import com.google.common.collect.ImmutableList;
 import static com.ning.billing.invoice.template.formatters.DefaultAmountFormatter.round;
 
 /**
- * Format invoice fields. Note that the Mustache engine won't accept null values.
+ * Format invoice fields
  */
 public class DefaultInvoiceFormatter implements InvoiceFormatter {
 
@@ -79,13 +83,68 @@ public class DefaultInvoiceFormatter implements InvoiceFormatter {
 
     @Override
     public List<InvoiceItem> getInvoiceItems() {
-        final List<InvoiceItem> formatters = new ArrayList<InvoiceItem>();
+        final List<InvoiceItem> invoiceItems = new ArrayList<InvoiceItem>();
+
+        InvoiceItem mergedCBAItem = null;
+        InvoiceItem mergedInvoiceAdjustment = null;
         for (final InvoiceItem item : invoice.getInvoiceItems()) {
+            if (InvoiceItemType.CBA_ADJ.equals(item.getInvoiceItemType())) {
+                // Merge CBA items to avoid confusing the customer, since these are internal
+                // adjustments (auto generated)
+                mergedCBAItem = mergeCBAItem(invoiceItems, mergedCBAItem, item);
+            } else if (InvoiceItemType.REFUND_ADJ.equals(item.getInvoiceItemType()) ||
+                       InvoiceItemType.CREDIT_ADJ.equals(item.getInvoiceItemType())) {
+                // Merge refund adjustments and credit adjustments, as these are both
+                // the same for the customer (invoice adjustment)
+                mergedInvoiceAdjustment = mergeInvoiceAdjustmentItem(invoiceItems, mergedInvoiceAdjustment, item);
+            } else {
+                invoiceItems.add(item);
+            }
+        }
+        if (mergedCBAItem != null) {
+            invoiceItems.add(mergedCBAItem);
+        }
+        if (mergedInvoiceAdjustment != null) {
+            invoiceItems.add(mergedInvoiceAdjustment);
+        }
+
+        final List<InvoiceItem> formatters = new ArrayList<InvoiceItem>();
+        for (final InvoiceItem item : invoiceItems) {
             formatters.add(new DefaultInvoiceItemFormatter(config, item, dateFormatter, locale));
         }
         return formatters;
     }
 
+    private InvoiceItem mergeCBAItem(final List<InvoiceItem> invoiceItems, InvoiceItem mergedCBAItem, final InvoiceItem item) {
+        if (mergedCBAItem == null) {
+            mergedCBAItem = item;
+        } else {
+            // This is really just to be safe - they should always have the same currency
+            if (!mergedCBAItem.getCurrency().equals(item.getCurrency())) {
+                invoiceItems.add(item);
+            } else {
+                mergedCBAItem = new CreditBalanceAdjInvoiceItem(invoice.getId(), invoice.getAccountId(), invoice.getInvoiceDate(),
+                                                                mergedCBAItem.getAmount().add(item.getAmount()), mergedCBAItem.getCurrency());
+            }
+        }
+        return mergedCBAItem;
+    }
+
+    private InvoiceItem mergeInvoiceAdjustmentItem(final List<InvoiceItem> invoiceItems, InvoiceItem mergedInvoiceAdjustment, final InvoiceItem item) {
+        if (mergedInvoiceAdjustment == null) {
+            mergedInvoiceAdjustment = item;
+        } else {
+            // This is really just to be safe - they should always have the same currency
+            if (!mergedInvoiceAdjustment.getCurrency().equals(item.getCurrency())) {
+                invoiceItems.add(item);
+            } else {
+                mergedInvoiceAdjustment = new CreditAdjInvoiceItem(invoice.getId(), invoice.getAccountId(), invoice.getInvoiceDate(),
+                                                                   mergedInvoiceAdjustment.getAmount().add(item.getAmount()), mergedInvoiceAdjustment.getCurrency());
+            }
+        }
+        return mergedInvoiceAdjustment;
+    }
+
     @Override
     public boolean addInvoiceItem(final InvoiceItem item) {
         return invoice.addInvoiceItem(item);
@@ -147,6 +206,24 @@ public class DefaultInvoiceFormatter implements InvoiceFormatter {
     }
 
     @Override
+    public String getFormattedChargedAmount() {
+        final NumberFormat number = NumberFormat.getCurrencyInstance(locale);
+        return number.format(getChargedAmount().doubleValue());
+    }
+
+    @Override
+    public String getFormattedPaidAmount() {
+        final NumberFormat number = NumberFormat.getCurrencyInstance(locale);
+        return number.format(getPaidAmount().doubleValue());
+    }
+
+    @Override
+    public String getFormattedBalance() {
+        final NumberFormat number = NumberFormat.getCurrencyInstance(locale);
+        return number.format(getBalance().doubleValue());
+    }
+
+    @Override
     public boolean isMigrationInvoice() {
         return invoice.isMigrationInvoice();
     }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
index 04982f4..b16a17e 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
@@ -17,15 +17,13 @@
 package com.ning.billing.invoice.template.formatters;
 
 import java.math.BigDecimal;
+import java.text.NumberFormat;
 import java.util.Locale;
 import java.util.UUID;
 
-import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
 import org.joda.time.format.DateTimeFormatter;
 
-import com.google.common.base.Objects;
-import com.google.common.base.Strings;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.invoice.api.InvoiceItemType;
@@ -34,12 +32,16 @@ import com.ning.billing.util.template.translation.DefaultCatalogTranslator;
 import com.ning.billing.util.template.translation.Translator;
 import com.ning.billing.util.template.translation.TranslatorConfig;
 
+import com.google.common.base.Objects;
+import com.google.common.base.Strings;
+
 import static com.ning.billing.invoice.template.formatters.DefaultAmountFormatter.round;
 
 /**
- * Format invoice item fields. Note that the Mustache engine won't accept null values.
+ * Format invoice item fields
  */
 public class DefaultInvoiceItemFormatter implements InvoiceItemFormatter {
+
     private final Translator translator;
 
     private final InvoiceItem item;
@@ -65,6 +67,13 @@ public class DefaultInvoiceItemFormatter implements InvoiceItemFormatter {
     }
 
     @Override
+    public String getFormattedAmount() {
+        final NumberFormat number = NumberFormat.getCurrencyInstance(locale);
+        number.setCurrency(java.util.Currency.getInstance(item.getCurrency().toString()));
+        return number.format(getAmount().doubleValue());
+    }
+
+    @Override
     public InvoiceItemType getInvoiceItemType() {
         return item.getInvoiceItemType();
     }
@@ -86,12 +95,12 @@ public class DefaultInvoiceItemFormatter implements InvoiceItemFormatter {
 
     @Override
     public String getFormattedStartDate() {
-        return Strings.nullToEmpty(item.getStartDate().toString(dateFormatter));
+        return item.getStartDate().toString(dateFormatter);
     }
 
     @Override
     public String getFormattedEndDate() {
-        return Strings.nullToEmpty(item.getEndDate() == null ? null : item.getEndDate().toString(dateFormatter));
+        return item.getEndDate() == null ? null : item.getEndDate().toString(dateFormatter);
     }
 
     @Override
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/HtmlInvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/template/HtmlInvoiceGenerator.java
index 7f55be3..9d105e5 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/template/HtmlInvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/HtmlInvoiceGenerator.java
@@ -28,6 +28,7 @@ import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.formatters.InvoiceFormatter;
 import com.ning.billing.invoice.api.formatters.InvoiceFormatterFactory;
 import com.ning.billing.invoice.template.translator.DefaultInvoiceTranslator;
+import com.ning.billing.util.LocaleUtils;
 import com.ning.billing.util.email.templates.TemplateEngine;
 import com.ning.billing.util.template.translation.TranslatorConfig;
 
@@ -51,7 +52,7 @@ public class HtmlInvoiceGenerator {
 
         final Map<String, Object> data = new HashMap<String, Object>();
         final DefaultInvoiceTranslator invoiceTranslator = new DefaultInvoiceTranslator(config);
-        final Locale locale = new Locale(account.getLocale());
+        final Locale locale = LocaleUtils.toLocale(account.getLocale());
         invoiceTranslator.setLocale(locale);
         data.put("text", invoiceTranslator);
         data.put("account", account);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
index 5a6b3a4..b45869d 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
@@ -300,4 +300,9 @@ public class MockInvoiceDao implements InvoiceDao {
             throws InvoiceApiException {
         return null;
     }
+
+    @Override
+    public void deleteCBA(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId, final CallContext context) throws InvoiceApiException {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDao.java b/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDao.java
index bc28352..54fce43 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDao.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDao.java
@@ -1319,4 +1319,169 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
         tags = tagDao.loadEntities(invoice.getId(), ObjectType.INVOICE);
         assertEquals(tags.size(), 0);
     }
+
+    @Test(groups = "slow")
+    public void testDeleteCBANotConsumed() throws Exception {
+        final UUID accountId = UUID.randomUUID();
+
+        // Create invoice 1
+        // Scenario: single item with payment
+        // * $10 item
+        // Then, a repair occur:
+        // * $-10 repair
+        // * $10 generated CBA due to the repair (assume previous payment)
+        final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
+        final InvoiceItem fixedItem1 = new FixedPriceInvoiceItem(invoice1.getId(), invoice1.getAccountId(), null, null, UUID.randomUUID().toString(),
+                                                                 UUID.randomUUID().toString(), clock.getUTCToday(), BigDecimal.TEN, Currency.USD);
+        final RepairAdjInvoiceItem repairAdjInvoiceItem = new RepairAdjInvoiceItem(fixedItem1.getInvoiceId(), fixedItem1.getAccountId(),
+                                                                                   fixedItem1.getStartDate(), fixedItem1.getEndDate(),
+                                                                                   fixedItem1.getAmount().negate(), fixedItem1.getCurrency(),
+                                                                                   fixedItem1.getId());
+        final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem1 = new CreditBalanceAdjInvoiceItem(fixedItem1.getInvoiceId(), fixedItem1.getAccountId(),
+                                                                                                         fixedItem1.getStartDate(), fixedItem1.getAmount(),
+                                                                                                         fixedItem1.getCurrency());
+        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), true, context);
+        invoiceItemSqlDao.create(fixedItem1, context);
+        invoiceItemSqlDao.create(repairAdjInvoiceItem, context);
+        invoiceItemSqlDao.create(creditBalanceAdjInvoiceItem1, context);
+
+        // Verify scenario - no CBA should have been used
+        Assert.assertEquals(invoiceDao.getAccountCBA(accountId).doubleValue(), 10.00);
+        verifyInvoice(invoice1.getId(), 10.00, 10.00);
+
+        // Delete the CBA on invoice 1
+        invoiceDao.deleteCBA(accountId, invoice1.getId(), creditBalanceAdjInvoiceItem1.getId(), context);
+
+        // Verify the result
+        Assert.assertEquals(invoiceDao.getAccountCBA(accountId).doubleValue(), 0.00);
+        verifyInvoice(invoice1.getId(), 0.00, 0.00);
+    }
+
+    @Test(groups = "slow")
+    public void testDeleteCBAPartiallyConsumed() throws Exception {
+        final UUID accountId = UUID.randomUUID();
+
+        // Create invoice 1
+        // Scenario: single item with payment
+        // * $10 item
+        // Then, a repair occur:
+        // * $-10 repair
+        // * $10 generated CBA due to the repair (assume previous payment)
+        final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
+        final InvoiceItem fixedItem1 = new FixedPriceInvoiceItem(invoice1.getId(), invoice1.getAccountId(), null, null, UUID.randomUUID().toString(),
+                                                                 UUID.randomUUID().toString(), clock.getUTCToday(), BigDecimal.TEN, Currency.USD);
+        final RepairAdjInvoiceItem repairAdjInvoiceItem = new RepairAdjInvoiceItem(fixedItem1.getInvoiceId(), fixedItem1.getAccountId(),
+                                                                                   fixedItem1.getStartDate(), fixedItem1.getEndDate(),
+                                                                                   fixedItem1.getAmount().negate(), fixedItem1.getCurrency(),
+                                                                                   fixedItem1.getId());
+        final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem1 = new CreditBalanceAdjInvoiceItem(fixedItem1.getInvoiceId(), fixedItem1.getAccountId(),
+                                                                                                         fixedItem1.getStartDate(), fixedItem1.getAmount(),
+                                                                                                         fixedItem1.getCurrency());
+        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), true, context);
+        invoiceItemSqlDao.create(fixedItem1, context);
+        invoiceItemSqlDao.create(repairAdjInvoiceItem, context);
+        invoiceItemSqlDao.create(creditBalanceAdjInvoiceItem1, context);
+
+        // Create invoice 2
+        // Scenario: single item
+        // * $5 item
+        // * $-5 CBA used
+        final DefaultInvoice invoice2 = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
+        final InvoiceItem fixedItem2 = new FixedPriceInvoiceItem(invoice2.getId(), invoice1.getAccountId(), null, null, UUID.randomUUID().toString(),
+                                                                 UUID.randomUUID().toString(), clock.getUTCToday(), new BigDecimal("5"), Currency.USD);
+        final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem2 = new CreditBalanceAdjInvoiceItem(fixedItem2.getInvoiceId(), fixedItem2.getAccountId(),
+                                                                                                         fixedItem2.getStartDate(), fixedItem2.getAmount().negate(),
+                                                                                                         fixedItem2.getCurrency());
+        invoiceDao.create(invoice2, invoice2.getTargetDate().getDayOfMonth(), true, context);
+        invoiceItemSqlDao.create(fixedItem2, context);
+        invoiceItemSqlDao.create(creditBalanceAdjInvoiceItem2, context);
+
+        // Verify scenario - half of the CBA should have been used
+        Assert.assertEquals(invoiceDao.getAccountCBA(accountId).doubleValue(), 5.00);
+        verifyInvoice(invoice1.getId(), 10.00, 10.00);
+        verifyInvoice(invoice2.getId(), 0.00, -5.00);
+
+        // Delete the CBA on invoice 1
+        invoiceDao.deleteCBA(accountId, invoice1.getId(), creditBalanceAdjInvoiceItem1.getId(), context);
+
+        // Verify all three invoices were affected
+        Assert.assertEquals(invoiceDao.getAccountCBA(accountId).doubleValue(), 0.00);
+        verifyInvoice(invoice1.getId(), 0.00, 0.00);
+        verifyInvoice(invoice2.getId(), 5.00, 0.00);
+    }
+
+    @Test(groups = "slow")
+    public void testDeleteCBAFullyConsumedTwice() throws Exception {
+        final UUID accountId = UUID.randomUUID();
+
+        // Create invoice 1
+        // Scenario: single item with payment
+        // * $10 item
+        // Then, a repair occur:
+        // * $-10 repair
+        // * $10 generated CBA due to the repair (assume previous payment)
+        final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
+        final InvoiceItem fixedItem1 = new FixedPriceInvoiceItem(invoice1.getId(), invoice1.getAccountId(), null, null, UUID.randomUUID().toString(),
+                                                                 UUID.randomUUID().toString(), clock.getUTCToday(), BigDecimal.TEN, Currency.USD);
+        final RepairAdjInvoiceItem repairAdjInvoiceItem = new RepairAdjInvoiceItem(fixedItem1.getInvoiceId(), fixedItem1.getAccountId(),
+                                                                                   fixedItem1.getStartDate(), fixedItem1.getEndDate(),
+                                                                                   fixedItem1.getAmount().negate(), fixedItem1.getCurrency(),
+                                                                                   fixedItem1.getId());
+        final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem1 = new CreditBalanceAdjInvoiceItem(fixedItem1.getInvoiceId(), fixedItem1.getAccountId(),
+                                                                                                         fixedItem1.getStartDate(), fixedItem1.getAmount(),
+                                                                                                         fixedItem1.getCurrency());
+        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), true, context);
+        invoiceItemSqlDao.create(fixedItem1, context);
+        invoiceItemSqlDao.create(repairAdjInvoiceItem, context);
+        invoiceItemSqlDao.create(creditBalanceAdjInvoiceItem1, context);
+
+        // Create invoice 2
+        // Scenario: single item
+        // * $5 item
+        // * $-5 CBA used
+        final DefaultInvoice invoice2 = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
+        final InvoiceItem fixedItem2 = new FixedPriceInvoiceItem(invoice2.getId(), invoice1.getAccountId(), null, null, UUID.randomUUID().toString(),
+                                                                 UUID.randomUUID().toString(), clock.getUTCToday(), new BigDecimal("5"), Currency.USD);
+        final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem2 = new CreditBalanceAdjInvoiceItem(fixedItem2.getInvoiceId(), fixedItem2.getAccountId(),
+                                                                                                         fixedItem2.getStartDate(), fixedItem2.getAmount().negate(),
+                                                                                                         fixedItem2.getCurrency());
+        invoiceDao.create(invoice2, invoice2.getTargetDate().getDayOfMonth(), true, context);
+        invoiceItemSqlDao.create(fixedItem2, context);
+        invoiceItemSqlDao.create(creditBalanceAdjInvoiceItem2, context);
+
+        // Create invoice 3
+        // Scenario: single item
+        // * $5 item
+        // * $-5 CBA used
+        final DefaultInvoice invoice3 = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
+        final InvoiceItem fixedItem3 = new FixedPriceInvoiceItem(invoice3.getId(), invoice1.getAccountId(), null, null, UUID.randomUUID().toString(),
+                                                                 UUID.randomUUID().toString(), clock.getUTCToday(), new BigDecimal("5"), Currency.USD);
+        final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem3 = new CreditBalanceAdjInvoiceItem(fixedItem3.getInvoiceId(), fixedItem3.getAccountId(),
+                                                                                                         fixedItem3.getStartDate(), fixedItem3.getAmount().negate(),
+                                                                                                         fixedItem3.getCurrency());
+        invoiceDao.create(invoice3, invoice3.getTargetDate().getDayOfMonth(), true, context);
+        invoiceItemSqlDao.create(fixedItem3, context);
+        invoiceItemSqlDao.create(creditBalanceAdjInvoiceItem3, context);
+
+        // Verify scenario - all CBA should have been used
+        Assert.assertEquals(invoiceDao.getAccountCBA(accountId).doubleValue(), 0.00);
+        verifyInvoice(invoice1.getId(), 10.00, 10.00);
+        verifyInvoice(invoice2.getId(), 0.00, -5.00);
+        verifyInvoice(invoice3.getId(), 0.00, -5.00);
+
+        // Delete the CBA on invoice 1
+        invoiceDao.deleteCBA(accountId, invoice1.getId(), creditBalanceAdjInvoiceItem1.getId(), context);
+
+        // Verify all three invoices were affected
+        Assert.assertEquals(invoiceDao.getAccountCBA(accountId).doubleValue(), 0.00);
+        verifyInvoice(invoice1.getId(), 0.00, 0.00);
+        verifyInvoice(invoice2.getId(), 5.00, 0.00);
+        verifyInvoice(invoice3.getId(), 5.00, 0.00);
+    }
+
+    private void verifyInvoice(final UUID invoiceId, final double balance, final double cbaAmount) throws InvoiceApiException {
+        final Invoice invoice = invoiceDao.getById(invoiceId);
+        Assert.assertEquals(invoice.getBalance().doubleValue(), balance);
+        Assert.assertEquals(invoice.getCBAAmount().doubleValue(), cbaAmount);
+    }
 }
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDaoForItemAdjustment.java b/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDaoForItemAdjustment.java
index 1fac6f7..5fc759d 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDaoForItemAdjustment.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDaoForItemAdjustment.java
@@ -108,7 +108,7 @@ public class TestInvoiceDaoForItemAdjustment extends InvoiceDaoTestBase {
         Assert.assertEquals(adjustedInvoiceItem.getAccountId(), invoiceItem.getAccountId());
         Assert.assertNull(adjustedInvoiceItem.getBundleId());
         Assert.assertEquals(adjustedInvoiceItem.getCurrency(), invoiceItem.getCurrency());
-        Assert.assertEquals(adjustedInvoiceItem.getDescription(), "item-adj");
+        Assert.assertEquals(adjustedInvoiceItem.getDescription(), "Invoice item adjustment");
         Assert.assertEquals(adjustedInvoiceItem.getEndDate(), effectiveDate);
         Assert.assertEquals(adjustedInvoiceItem.getInvoiceId(), invoiceItem.getInvoiceId());
         Assert.assertEquals(adjustedInvoiceItem.getInvoiceItemType(), InvoiceItemType.ITEM_ADJ);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java b/invoice/src/test/java/com/ning/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java
new file mode 100644
index 0000000..0f12045
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.template.formatters;
+
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+import org.skife.config.ConfigurationObjectFactory;
+import org.testng.Assert;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.InvoiceTestSuite;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceItemType;
+import com.ning.billing.invoice.api.formatters.InvoiceFormatter;
+import com.ning.billing.invoice.model.CreditAdjInvoiceItem;
+import com.ning.billing.invoice.model.CreditBalanceAdjInvoiceItem;
+import com.ning.billing.invoice.model.DefaultInvoice;
+import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
+import com.ning.billing.invoice.model.RefundAdjInvoiceItem;
+import com.ning.billing.invoice.model.RepairAdjInvoiceItem;
+import com.ning.billing.util.email.templates.MustacheTemplateEngine;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+
+public class TestDefaultInvoiceFormatter extends InvoiceTestSuite {
+
+    private TranslatorConfig config;
+    private MustacheTemplateEngine templateEngine;
+
+    @BeforeSuite(groups = "fast")
+    public void setup() {
+        config = new ConfigurationObjectFactory(System.getProperties()).build(TranslatorConfig.class);
+        templateEngine = new MustacheTemplateEngine();
+    }
+
+    @Test(groups = "fast")
+    public void testMergeItems() throws Exception {
+        // Scenario: single item with payment
+        // * $10 item
+        // Then, a repair occur:
+        // * $-10 repair
+        // * $10 generated CBA due to the repair (assume previous payment)
+        // Then, the invoice is adjusted for $1:
+        // * $-1 credit adjustment
+        // * $1 generated CBA due to the credit adjustment
+        // Then, we refund $1 with invoice level adjustment:
+        // * $-1 refund adjustment
+        final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
+                                                                          UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                          new LocalDate(), BigDecimal.TEN, Currency.USD);
+        final RepairAdjInvoiceItem repairAdjInvoiceItem = new RepairAdjInvoiceItem(fixedItem.getInvoiceId(), fixedItem.getAccountId(),
+                                                                                   fixedItem.getStartDate(), fixedItem.getEndDate(),
+                                                                                   fixedItem.getAmount().negate(), fixedItem.getCurrency(),
+                                                                                   fixedItem.getId());
+        final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem = new CreditBalanceAdjInvoiceItem(fixedItem.getInvoiceId(), fixedItem.getAccountId(),
+                                                                                                        fixedItem.getStartDate(), fixedItem.getAmount(),
+                                                                                                        fixedItem.getCurrency());
+        final CreditAdjInvoiceItem creditAdjInvoiceItem = new CreditAdjInvoiceItem(fixedItem.getInvoiceId(), fixedItem.getAccountId(),
+                                                                                   fixedItem.getStartDate(), BigDecimal.ONE.negate(), fixedItem.getCurrency());
+        final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem2 = new CreditBalanceAdjInvoiceItem(fixedItem.getInvoiceId(), fixedItem.getAccountId(),
+                                                                                                         fixedItem.getStartDate(), creditAdjInvoiceItem.getAmount().negate(),
+                                                                                                         fixedItem.getCurrency());
+        final RefundAdjInvoiceItem refundAdjInvoiceItem = new RefundAdjInvoiceItem(fixedItem.getInvoiceId(), fixedItem.getAccountId(),
+                                                                                   fixedItem.getStartDate(), BigDecimal.ONE.negate(), fixedItem.getCurrency());
+        final Invoice invoice = new DefaultInvoice(fixedItem.getInvoiceId(), fixedItem.getAccountId(), null,
+                                                   new LocalDate(), new LocalDate(), Currency.USD, false);
+        invoice.addInvoiceItem(fixedItem);
+        invoice.addInvoiceItem(repairAdjInvoiceItem);
+        invoice.addInvoiceItem(creditBalanceAdjInvoiceItem);
+        invoice.addInvoiceItem(creditAdjInvoiceItem);
+        invoice.addInvoiceItem(creditBalanceAdjInvoiceItem2);
+        invoice.addInvoiceItem(refundAdjInvoiceItem);
+        // Check the scenario
+        Assert.assertEquals(invoice.getBalance().doubleValue(), 9.00);
+        Assert.assertEquals(invoice.getCBAAmount().doubleValue(), 11.00);
+        Assert.assertEquals(invoice.getRefundAdjAmount().doubleValue(), -1.00);
+
+        // Verify the merge
+        final InvoiceFormatter formatter = new DefaultInvoiceFormatter(config, invoice, Locale.US);
+        final List<InvoiceItem> invoiceItems = formatter.getInvoiceItems();
+        Assert.assertEquals(invoiceItems.size(), 4);
+        Assert.assertEquals(invoiceItems.get(0).getInvoiceItemType(), InvoiceItemType.FIXED);
+        Assert.assertEquals(invoiceItems.get(0).getAmount().doubleValue(), 10.00);
+        Assert.assertEquals(invoiceItems.get(1).getInvoiceItemType(), InvoiceItemType.REPAIR_ADJ);
+        Assert.assertEquals(invoiceItems.get(1).getAmount().doubleValue(), -10.00);
+        Assert.assertEquals(invoiceItems.get(2).getInvoiceItemType(), InvoiceItemType.CBA_ADJ);
+        Assert.assertEquals(invoiceItems.get(2).getAmount().doubleValue(), 11.00);
+        Assert.assertEquals(invoiceItems.get(3).getInvoiceItemType(), InvoiceItemType.CREDIT_ADJ);
+        Assert.assertEquals(invoiceItems.get(3).getAmount().doubleValue(), -2.00);
+    }
+
+    @Test(groups = "fast")
+    public void testFormattedAmount() throws Exception {
+        final FixedPriceInvoiceItem fixedItemEUR = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
+                                                                             UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                             new LocalDate(), new BigDecimal("1499.95"), Currency.EUR);
+        final Invoice invoiceEUR = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.EUR);
+        invoiceEUR.addInvoiceItem(fixedItemEUR);
+
+        checkOutput(invoiceEUR,
+                    "<tr>\n" +
+                    "    <td class=\"amount\"><strong>{{invoice.formattedChargedAmount}}</strong></td>\n" +
+                    "</tr>\n" +
+                    "<tr>\n" +
+                    "    <td class=\"amount\"><strong>{{invoice.formattedPaidAmount}}</strong></td>\n" +
+                    "</tr>\n" +
+                    "<tr>\n" +
+                    "    <td class=\"amount\"><strong>{{invoice.formattedBalance}}</strong></td>\n" +
+                    "</tr>",
+                    "<tr>\n" +
+                    "    <td class=\"amount\"><strong>1 499,95 €</strong></td>\n" +
+                    "</tr>\n" +
+                    "<tr>\n" +
+                    "    <td class=\"amount\"><strong>0,00 €</strong></td>\n" +
+                    "</tr>\n" +
+                    "<tr>\n" +
+                    "    <td class=\"amount\"><strong>1 499,95 €</strong></td>\n" +
+                    "</tr>",
+                    Locale.FRANCE);
+    }
+
+    private void checkOutput(final Invoice invoice, final String template, final String expected, final Locale locale) {
+        final Map<String, Object> data = new HashMap<String, Object>();
+        data.put("invoice", new DefaultInvoiceFormatter(config, invoice, locale));
+
+        final String formattedText = templateEngine.executeTemplateText(template, data);
+        Assert.assertEquals(formattedText, expected);
+    }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/template/formatters/TestDefaultInvoiceItemFormatter.java b/invoice/src/test/java/com/ning/billing/invoice/template/formatters/TestDefaultInvoiceItemFormatter.java
new file mode 100644
index 0000000..e91f9be
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/template/formatters/TestDefaultInvoiceItemFormatter.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.template.formatters;
+
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormat;
+import org.skife.config.ConfigurationObjectFactory;
+import org.testng.Assert;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.InvoiceTestSuite;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.util.LocaleUtils;
+import com.ning.billing.util.email.templates.MustacheTemplateEngine;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+
+public class TestDefaultInvoiceItemFormatter extends InvoiceTestSuite {
+
+    private TranslatorConfig config;
+    private MustacheTemplateEngine templateEngine;
+
+    @BeforeSuite(groups = "fast")
+    public void setup() {
+        config = new ConfigurationObjectFactory(System.getProperties()).build(TranslatorConfig.class);
+        templateEngine = new MustacheTemplateEngine();
+    }
+
+    @Test(groups = "fast")
+    public void testBasicUSD() throws Exception {
+        final FixedPriceInvoiceItem fixedItemUSD = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
+                                                                             UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                             new LocalDate(), new BigDecimal("-1114.751625346"), Currency.USD);
+        checkOutput(fixedItemUSD, "{{#invoiceItem}}<td class=\"amount\">{{formattedAmount}}</td>{{/invoiceItem}}",
+                    "<td class=\"amount\">($1,114.75)</td>", LocaleUtils.toLocale("en_US"));
+    }
+
+    @Test(groups = "fast")
+    public void testFormattedAmount() throws Exception {
+        final FixedPriceInvoiceItem fixedItemEUR = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
+                                                                             UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                             new LocalDate(), new BigDecimal("1499.95"), Currency.EUR);
+        checkOutput(fixedItemEUR, "{{#invoiceItem}}<td class=\"amount\">{{formattedAmount}}</td>{{/invoiceItem}}",
+                    "<td class=\"amount\">1 499,95 €</td>", Locale.FRANCE);
+
+        final FixedPriceInvoiceItem fixedItemUSD = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
+                                                                             UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                             new LocalDate(), new BigDecimal("-1114.751625346"), Currency.USD);
+        checkOutput(fixedItemUSD, "{{#invoiceItem}}<td class=\"amount\">{{formattedAmount}}</td>{{/invoiceItem}}", "<td class=\"amount\">($1,114.75)</td>");
+
+        // Check locale/currency mismatch (locale is set at the account level)
+        final FixedPriceInvoiceItem fixedItemGBP = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
+                                                                             UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                             new LocalDate(), new BigDecimal("8.07"), Currency.GBP);
+        checkOutput(fixedItemGBP, "{{#invoiceItem}}<td class=\"amount\">{{formattedAmount}}</td>{{/invoiceItem}}",
+                    "<td class=\"amount\">8,07 GBP</td>", Locale.FRANCE);
+    }
+
+    @Test(groups = "fast")
+    public void testNullEndDate() throws Exception {
+        final LocalDate startDate = new LocalDate(2012, 12, 1);
+        final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
+                                                                          UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                          startDate, BigDecimal.TEN, Currency.USD);
+        checkOutput(fixedItem,
+                    "{{#invoiceItem}}<td>{{formattedStartDate}}{{#formattedEndDate}} - {{formattedEndDate}}{{/formattedEndDate}}</td>{{/invoiceItem}}",
+                    "<td>Dec 1, 2012</td>");
+    }
+
+    @Test(groups = "fast")
+    public void testNonNullEndDate() throws Exception {
+        final LocalDate startDate = new LocalDate(2012, 12, 1);
+        final LocalDate endDate = new LocalDate(2012, 12, 31);
+        final RecurringInvoiceItem recurringItem = new RecurringInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
+                                                                            UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                            startDate, endDate, BigDecimal.TEN, BigDecimal.TEN, Currency.USD);
+        checkOutput(recurringItem,
+                    "{{#invoiceItem}}<td>{{formattedStartDate}}{{#formattedEndDate}} - {{formattedEndDate}}{{/formattedEndDate}}</td>{{/invoiceItem}}",
+                    "<td>Dec 1, 2012 - Dec 31, 2012</td>");
+    }
+
+    private void checkOutput(final InvoiceItem invoiceItem, final String template, final String expected) {
+        checkOutput(invoiceItem, template, expected, Locale.US);
+    }
+
+    private void checkOutput(final InvoiceItem invoiceItem, final String template, final String expected, final Locale locale) {
+        final Map<String, Object> data = new HashMap<String, Object>();
+        data.put("invoiceItem", new DefaultInvoiceItemFormatter(config, invoiceItem, DateTimeFormat.mediumDate(), locale));
+
+        final String formattedText = templateEngine.executeTemplateText(template, data);
+        Assert.assertEquals(formattedText, expected);
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJsonWithBalance.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJsonWithBalance.java
index 618f924..5235fcb 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJsonWithBalance.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJsonWithBalance.java
@@ -13,14 +13,15 @@
  * License for the specific language governing permissions and limitations
  * under the License.
  */
+
 package com.ning.billing.jaxrs.json;
 
 import java.math.BigDecimal;
 
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
 import com.ning.billing.account.api.Account;
 
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
 
 public class AccountJsonWithBalance extends AccountJson {
 
@@ -33,29 +34,29 @@ public class AccountJsonWithBalance extends AccountJson {
 
     @JsonCreator
     public AccountJsonWithBalance(@JsonProperty("accountId") final String accountId,
-                       @JsonProperty("name") final String name,
-                       @JsonProperty("firstNameLength") final Integer length,
-                       @JsonProperty("externalKey") final String externalKey,
-                       @JsonProperty("email") final String email,
-                       @JsonProperty("billCycleDay") final BillCycleDayJson billCycleDay,
-                       @JsonProperty("currency") final String currency,
-                       @JsonProperty("paymentMethodId") final String paymentMethodId,
-                       @JsonProperty("timezone") final String timeZone,
-                       @JsonProperty("address1") final String address1,
-                       @JsonProperty("address2") final String address2,
-                       @JsonProperty("postalCode") final String postalCode,
-                       @JsonProperty("company") final String company,
-                       @JsonProperty("city") final String city,
-                       @JsonProperty("state") final String state,
-                       @JsonProperty("country") final String country,
-                       @JsonProperty("locale") final String locale,
-                       @JsonProperty("phone") final String phone,
-                       @JsonProperty("isMigrated") final Boolean isMigrated,
-                       @JsonProperty("isNotifiedForInvoices") final Boolean isNotifiedForInvoices,
-                       @JsonProperty("accountBalance") final BigDecimal accountBalance) {
-            super(accountId, name, length, externalKey, email, billCycleDay, currency, paymentMethodId, timeZone,
-                    address1, address2, postalCode, company, city, state, country, locale, phone, isMigrated, isNotifiedForInvoices);
-            this.accountBalance = accountBalance;
+                                  @JsonProperty("name") final String name,
+                                  @JsonProperty("firstNameLength") final Integer length,
+                                  @JsonProperty("externalKey") final String externalKey,
+                                  @JsonProperty("email") final String email,
+                                  @JsonProperty("billCycleDay") final BillCycleDayJson billCycleDay,
+                                  @JsonProperty("currency") final String currency,
+                                  @JsonProperty("paymentMethodId") final String paymentMethodId,
+                                  @JsonProperty("timezone") final String timeZone,
+                                  @JsonProperty("address1") final String address1,
+                                  @JsonProperty("address2") final String address2,
+                                  @JsonProperty("postalCode") final String postalCode,
+                                  @JsonProperty("company") final String company,
+                                  @JsonProperty("city") final String city,
+                                  @JsonProperty("state") final String state,
+                                  @JsonProperty("country") final String country,
+                                  @JsonProperty("locale") final String locale,
+                                  @JsonProperty("phone") final String phone,
+                                  @JsonProperty("isMigrated") final Boolean isMigrated,
+                                  @JsonProperty("isNotifiedForInvoices") final Boolean isNotifiedForInvoices,
+                                  @JsonProperty("accountBalance") final BigDecimal accountBalance) {
+        super(accountId, name, length, externalKey, email, billCycleDay, currency, paymentMethodId, timeZone,
+              address1, address2, postalCode, company, city, state, country, locale, phone, isMigrated, isNotifiedForInvoices);
+        this.accountBalance = accountBalance;
     }
 
     public BigDecimal getAccountBalance() {
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJsonWithBalanceAndCBA.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJsonWithBalanceAndCBA.java
new file mode 100644
index 0000000..17f0acb
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJsonWithBalanceAndCBA.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.jaxrs.json;
+
+import java.math.BigDecimal;
+
+import com.ning.billing.account.api.Account;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class AccountJsonWithBalanceAndCBA extends AccountJsonWithBalance {
+
+    private final BigDecimal accountCBA;
+
+    public AccountJsonWithBalanceAndCBA(final Account account, final BigDecimal accountBalance, final BigDecimal accountCBA) {
+        super(account, accountBalance);
+        this.accountCBA = accountCBA;
+    }
+
+    @JsonCreator
+    public AccountJsonWithBalanceAndCBA(@JsonProperty("accountId") final String accountId,
+                                        @JsonProperty("name") final String name,
+                                        @JsonProperty("firstNameLength") final Integer length,
+                                        @JsonProperty("externalKey") final String externalKey,
+                                        @JsonProperty("email") final String email,
+                                        @JsonProperty("billCycleDay") final BillCycleDayJson billCycleDay,
+                                        @JsonProperty("currency") final String currency,
+                                        @JsonProperty("paymentMethodId") final String paymentMethodId,
+                                        @JsonProperty("timezone") final String timeZone,
+                                        @JsonProperty("address1") final String address1,
+                                        @JsonProperty("address2") final String address2,
+                                        @JsonProperty("postalCode") final String postalCode,
+                                        @JsonProperty("company") final String company,
+                                        @JsonProperty("city") final String city,
+                                        @JsonProperty("state") final String state,
+                                        @JsonProperty("country") final String country,
+                                        @JsonProperty("locale") final String locale,
+                                        @JsonProperty("phone") final String phone,
+                                        @JsonProperty("isMigrated") final Boolean isMigrated,
+                                        @JsonProperty("isNotifiedForInvoices") final Boolean isNotifiedForInvoices,
+                                        @JsonProperty("accountBalance") final BigDecimal accountBalance,
+                                        @JsonProperty("accountCBA") final BigDecimal accountCBA) {
+        super(accountId, name, length, externalKey, email, billCycleDay, currency, paymentMethodId, timeZone, address1,
+              address2, postalCode, company, city, state, country, locale, phone, isMigrated, isNotifiedForInvoices, accountBalance);
+        this.accountCBA = accountCBA;
+    }
+
+    public BigDecimal getAccountCBA() {
+        return accountCBA;
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
index ee4a6fc..6d186d3 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
@@ -60,6 +60,7 @@ import com.ning.billing.invoice.api.InvoiceUserApi;
 import com.ning.billing.jaxrs.json.AccountEmailJson;
 import com.ning.billing.jaxrs.json.AccountJson;
 import com.ning.billing.jaxrs.json.AccountJsonWithBalance;
+import com.ning.billing.jaxrs.json.AccountJsonWithBalanceAndCBA;
 import com.ning.billing.jaxrs.json.AccountTimelineJson;
 import com.ning.billing.jaxrs.json.BundleJsonNoSubscriptions;
 import com.ning.billing.jaxrs.json.CustomFieldJson;
@@ -136,17 +137,10 @@ public class AccountResource extends JaxRsResourceBase {
     @Path("/{accountId:" + UUID_PATTERN + "}")
     @Produces(APPLICATION_JSON)
     public Response getAccount(@PathParam("accountId") final String accountId,
-            @QueryParam(QUERY_ACCOUNT_WITH_BALANCE) @DefaultValue("false") final Boolean accountWithBalance) throws AccountApiException {
+                               @QueryParam(QUERY_ACCOUNT_WITH_BALANCE) @DefaultValue("false") final Boolean accountWithBalance,
+                               @QueryParam(QUERY_ACCOUNT_WITH_BALANCE_AND_CBA) @DefaultValue("false") final Boolean accountWithBalanceAndCBA) throws AccountApiException {
         final Account account = accountApi.getAccountById(UUID.fromString(accountId));
-
-        AccountJson json = null;
-        if (accountWithBalance) {
-            final BigDecimal accountBalance = invoiceApi.getAccountBalance(account.getId());
-            json = new AccountJsonWithBalance(account, accountBalance);
-        } else {
-            json = new AccountJson(account);
-        }
-        return Response.status(Status.OK).entity(json).build();
+        return getAccount(account, accountWithBalance, accountWithBalanceAndCBA);
     }
 
     @GET
@@ -177,16 +171,24 @@ public class AccountResource extends JaxRsResourceBase {
     @GET
     @Produces(APPLICATION_JSON)
     public Response getAccountByKey(@QueryParam(QUERY_EXTERNAL_KEY) final String externalKey,
-            @QueryParam(QUERY_ACCOUNT_WITH_BALANCE) @DefaultValue("false") final Boolean accountWithBalance) throws AccountApiException {
+                                    @QueryParam(QUERY_ACCOUNT_WITH_BALANCE) @DefaultValue("false") final Boolean accountWithBalance,
+                                    @QueryParam(QUERY_ACCOUNT_WITH_BALANCE_AND_CBA) @DefaultValue("false") final Boolean accountWithBalanceAndCBA) throws AccountApiException {
         final Account account = accountApi.getAccountByKey(externalKey);
-        AccountJson json = null;
-        if (accountWithBalance) {
+        return getAccount(account, accountWithBalance, accountWithBalanceAndCBA);
+    }
+
+    private Response getAccount(final Account account, final Boolean accountWithBalance, final Boolean accountWithBalanceAndCBA) {
+        final AccountJson json;
+        if (accountWithBalanceAndCBA) {
+            final BigDecimal accountBalance = invoiceApi.getAccountBalance(account.getId());
+            final BigDecimal accountCBA = invoiceApi.getAccountCBA(account.getId());
+            json = new AccountJsonWithBalanceAndCBA(account, accountBalance, accountCBA);
+        } else if (accountWithBalance) {
             final BigDecimal accountBalance = invoiceApi.getAccountBalance(account.getId());
             json = new AccountJsonWithBalance(account, accountBalance);
         } else {
             json = new AccountJson(account);
         }
-
         return Response.status(Status.OK).entity(json).build();
     }
 
@@ -214,7 +216,7 @@ public class AccountResource extends JaxRsResourceBase {
         final AccountData data = json.toAccountData();
         final UUID uuid = UUID.fromString(accountId);
         accountApi.updateAccount(uuid, data, context.createContext(createdBy, reason, comment));
-        return getAccount(accountId, false);
+        return getAccount(accountId, false, false);
     }
 
     // Not supported
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
index aafebdb..0f3531b 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
@@ -18,6 +18,7 @@ package com.ning.billing.jaxrs.resources;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.UUID;
@@ -180,6 +181,24 @@ public class InvoiceResource extends JaxRsResourceBase {
         }
     }
 
+    @DELETE
+    @Path("/{invoiceId:" + UUID_PATTERN + "}" + "/{invoiceItemId:" + UUID_PATTERN + "}/cba")
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    public Response deleteCBA(@PathParam("invoiceId") final String invoiceId,
+                              @PathParam("invoiceItemId") final String invoiceItemId,
+                              @QueryParam(QUERY_ACCOUNT_ID) final String accountId,
+                              @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                              @HeaderParam(HDR_REASON) final String reason,
+                              @HeaderParam(HDR_COMMENT) final String comment) throws AccountApiException, InvoiceApiException {
+        final Account account = accountApi.getAccountById(UUID.fromString(accountId));
+
+        invoiceApi.deleteCBA(account.getId(), UUID.fromString(invoiceId), UUID.fromString(invoiceItemId),
+                             context.createContext(createdBy, reason, comment));
+
+        return Response.status(Status.OK).build();
+    }
+
     @POST
     @Path("/{invoiceId:" + UUID_PATTERN + "}")
     @Consumes(APPLICATION_JSON)
@@ -314,6 +333,30 @@ public class InvoiceResource extends JaxRsResourceBase {
     @POST
     @Produces(APPLICATION_JSON)
     @Consumes(APPLICATION_JSON)
+    @Path("/" + PAYMENTS)
+    public Response payAllInvoices(final PaymentJsonSimple payment,
+                                   @QueryParam(QUERY_PAYMENT_EXTERNAL) @DefaultValue("false") final Boolean externalPayment,
+                                   @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                                   @HeaderParam(HDR_REASON) final String reason,
+                                   @HeaderParam(HDR_COMMENT) final String comment) throws AccountApiException, PaymentApiException {
+        final Account account = accountApi.getAccountById(UUID.fromString(payment.getAccountId()));
+
+        final CallContext callContext = context.createContext(createdBy, reason, comment);
+        final Collection<Invoice> unpaidInvoices = invoiceApi.getUnpaidInvoicesByAccountId(account.getId(), clock.getUTCToday());
+        for (final Invoice invoice : unpaidInvoices) {
+            if (externalPayment) {
+                paymentApi.createExternalPayment(account, invoice.getId(), invoice.getBalance(), callContext);
+            } else {
+                paymentApi.createPayment(account, invoice.getId(), invoice.getBalance(), callContext);
+            }
+        }
+
+        return Response.status(Status.OK).build();
+    }
+
+    @POST
+    @Produces(APPLICATION_JSON)
+    @Consumes(APPLICATION_JSON)
     @Path("/{invoiceId:" + UUID_PATTERN + "}/" + PAYMENTS)
     public Response createInstantPayment(final PaymentJsonSimple payment,
                                          @QueryParam(QUERY_PAYMENT_EXTERNAL) @DefaultValue("false") final Boolean externalPayment,
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
index 56f7140..9c8af32 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
@@ -51,6 +51,7 @@ public interface JaxrsResource {
     public static final String QUERY_POLICY = "policy";
 
     public static final String QUERY_ACCOUNT_WITH_BALANCE = "accountWithBalance";
+    public static final String QUERY_ACCOUNT_WITH_BALANCE_AND_CBA = "accountWithBalanceAndCBA";
 
     public static final String QUERY_ACCOUNT_ID = "accountId";
 
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java b/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
index 712baeb..000c896 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
@@ -86,6 +86,30 @@ public class TestInvoice extends TestJaxrsBase {
     }
 
     @Test(groups = "slow")
+    public void testPayAllInvoices() throws Exception {
+        clock.setTime(new DateTime(2012, 4, 25, 0, 3, 42, 0));
+
+        // No payment method
+        final AccountJson accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice();
+
+        // Check there was no payment made
+        assertEquals(getPaymentsForAccount(accountJson.getAccountId()).size(), 0);
+
+        // Get the invoices
+        final List<InvoiceJsonSimple> invoices = getInvoicesForAccount(accountJson.getAccountId());
+        assertEquals(invoices.size(), 2);
+        final InvoiceJsonSimple invoiceToPay = invoices.get(1);
+        assertEquals(invoiceToPay.getBalance().compareTo(BigDecimal.ZERO), 1);
+
+        // Pay all invoices
+        payAllInvoices(accountJson, true);
+        for (final InvoiceJsonSimple invoice : getInvoicesForAccount(accountJson.getAccountId())) {
+            assertEquals(invoice.getBalance().compareTo(BigDecimal.ZERO), 0);
+        }
+        assertEquals(getPaymentsForAccount(accountJson.getAccountId()).size(), 1);
+    }
+
+    @Test(groups = "slow")
     public void testInvoiceCreatePayment() throws Exception {
         clock.setTime(new DateTime(2012, 4, 25, 0, 3, 42, 0));
 
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
index 0426343..ab631f2 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
@@ -784,6 +784,15 @@ public class TestJaxrsBase extends ServerTestSuiteWithEmbeddedDB {
         return objFromJson;
     }
 
+    protected void payAllInvoices(final AccountJson accountJson, final Boolean externalPayment) throws IOException {
+        final PaymentJsonSimple payment = new PaymentJsonSimple(null, null, accountJson.getAccountId(), null, null, null, null,
+                                                                null, 0, null, null, null, null, null, null, null);
+        final String postJson = mapper.writeValueAsString(payment);
+
+        final String uri = JaxrsResource.INVOICES_PATH + "/" + JaxrsResource.PAYMENTS;
+        doPost(uri, postJson, ImmutableMap.<String, String>of("externalPayment", externalPayment.toString()), DEFAULT_HTTP_TIMEOUT_SEC);
+    }
+
     protected List<PaymentJsonSimple> createInstaPayment(final AccountJson accountJson, final InvoiceJsonSimple invoice) throws IOException {
         final PaymentJsonSimple payment = new PaymentJsonSimple(invoice.getAmount(), BigDecimal.ZERO, accountJson.getAccountId(),
                                                                 invoice.getInvoiceId(), null, null, null, null, 0, null, null, null, null, null, null, null);
diff --git a/util/src/main/java/com/ning/billing/util/email/templates/MustacheTemplateEngine.java b/util/src/main/java/com/ning/billing/util/email/templates/MustacheTemplateEngine.java
index f6a76a3..8ddc2aa 100644
--- a/util/src/main/java/com/ning/billing/util/email/templates/MustacheTemplateEngine.java
+++ b/util/src/main/java/com/ning/billing/util/email/templates/MustacheTemplateEngine.java
@@ -23,13 +23,21 @@ import java.util.Map;
 
 import com.ning.billing.util.config.UriAccessor;
 import com.ning.billing.util.io.IOUtils;
+
+import com.google.common.annotations.VisibleForTesting;
 import com.samskivert.mustache.Mustache;
 import com.samskivert.mustache.Template;
 
 public class MustacheTemplateEngine implements TemplateEngine {
+
     @Override
     public String executeTemplate(final String templateName, final Map<String, Object> data) throws IOException {
         final String templateText = getTemplateText(templateName);
+        return executeTemplateText(templateText, data);
+    }
+
+    @VisibleForTesting
+    public String executeTemplateText(final String templateText, final Map<String, Object> data) {
         final Template template = Mustache.compiler().compile(templateText);
         return template.execute(data);
     }
diff --git a/util/src/main/java/com/ning/billing/util/LocaleUtils.java b/util/src/main/java/com/ning/billing/util/LocaleUtils.java
new file mode 100644
index 0000000..cbf1c13
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/LocaleUtils.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util;
+
+import java.util.Locale;
+
+public class LocaleUtils {
+
+    private LocaleUtils() {
+    }
+
+    // From commons-lang
+    public static Locale toLocale(final String str) {
+        if (str == null) {
+            return null;
+        }
+        final int len = str.length();
+        if (len != 2 && len != 5 && len < 7) {
+            throw new IllegalArgumentException("Invalid locale format: " + str);
+        }
+        final char ch0 = str.charAt(0);
+        final char ch1 = str.charAt(1);
+        if (ch0 < 'a' || ch0 > 'z' || ch1 < 'a' || ch1 > 'z') {
+            throw new IllegalArgumentException("Invalid locale format: " + str);
+        }
+        if (len == 2) {
+            return new Locale(str, "");
+        } else {
+            if (str.charAt(2) != '_') {
+                throw new IllegalArgumentException("Invalid locale format: " + str);
+            }
+            final char ch3 = str.charAt(3);
+            if (ch3 == '_') {
+                return new Locale(str.substring(0, 2), "", str.substring(4));
+            }
+            final char ch4 = str.charAt(4);
+            if (ch3 < 'A' || ch3 > 'Z' || ch4 < 'A' || ch4 > 'Z') {
+                throw new IllegalArgumentException("Invalid locale format: " + str);
+            }
+            if (len == 5) {
+                return new Locale(str.substring(0, 2), str.substring(3, 5));
+            } else {
+                if (str.charAt(5) != '_') {
+                    throw new IllegalArgumentException("Invalid locale format: " + str);
+                }
+                return new Locale(str.substring(0, 2), str.substring(3, 5), str.substring(6));
+            }
+        }
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/template/translation/DefaultTranslatorBase.java b/util/src/main/java/com/ning/billing/util/template/translation/DefaultTranslatorBase.java
index 72d3d90..d21e5ad 100644
--- a/util/src/main/java/com/ning/billing/util/template/translation/DefaultTranslatorBase.java
+++ b/util/src/main/java/com/ning/billing/util/template/translation/DefaultTranslatorBase.java
@@ -29,6 +29,7 @@ import org.slf4j.LoggerFactory;
 
 import com.google.inject.Inject;
 import com.ning.billing.ErrorCode;
+import com.ning.billing.util.LocaleUtils;
 import com.ning.billing.util.config.UriAccessor;
 
 public abstract class DefaultTranslatorBase implements Translator {
@@ -60,7 +61,7 @@ public abstract class DefaultTranslatorBase implements Translator {
                 return originalText;
             }
 
-            final Locale defaultLocale = new Locale(config.getDefaultLocale());
+            final Locale defaultLocale = LocaleUtils.toLocale(config.getDefaultLocale());
             try {
                 bundle = getBundle(defaultLocale, bundlePath);