killbill-memoizeit

Details

diff --git a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
index 8ae278a..7ebb8d2 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -17,24 +19,22 @@
 package org.killbill.billing.invoice.template.formatters;
 
 import java.math.BigDecimal;
-import java.text.NumberFormat;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.UUID;
 
+import org.joda.money.CurrencyUnit;
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
 import org.joda.time.format.DateTimeFormat;
 import org.joda.time.format.DateTimeFormatter;
 import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.billing.invoice.api.formatters.ResourceBundleFactory;
-import org.killbill.billing.tenant.api.TenantInternalApi;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.currency.api.CurrencyConversion;
 import org.killbill.billing.currency.api.CurrencyConversionApi;
@@ -45,23 +45,24 @@ import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoiceItemType;
 import org.killbill.billing.invoice.api.InvoicePayment;
 import org.killbill.billing.invoice.api.formatters.InvoiceFormatter;
+import org.killbill.billing.invoice.api.formatters.ResourceBundleFactory;
 import org.killbill.billing.invoice.model.CreditAdjInvoiceItem;
 import org.killbill.billing.invoice.model.CreditBalanceAdjInvoiceItem;
 import org.killbill.billing.invoice.model.DefaultInvoice;
 import org.killbill.billing.util.template.translation.TranslatorConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 
-import static org.killbill.billing.util.DefaultAmountFormatter.round;
-
 /**
  * Format invoice fields
  */
 public class DefaultInvoiceFormatter implements InvoiceFormatter {
 
-    private final static Logger logger = LoggerFactory.getLogger(DefaultInvoiceFormatter.class);
+    private static final Logger logger = LoggerFactory.getLogger(DefaultInvoiceFormatter.class);
 
     private final TranslatorConfig config;
     private final Invoice invoice;
@@ -70,8 +71,11 @@ public class DefaultInvoiceFormatter implements InvoiceFormatter {
     private final CurrencyConversionApi currencyConversionApi;
     private final InternalTenantContext context;
     private final ResourceBundleFactory bundleFactory;
+    private final Map<java.util.Currency, Locale> currencyLocaleMap;
 
-    public DefaultInvoiceFormatter(final TranslatorConfig config, final Invoice invoice, final Locale locale, final CurrencyConversionApi currencyConversionApi, final ResourceBundleFactory bundleFactory, final InternalTenantContext context) {
+    public DefaultInvoiceFormatter(final TranslatorConfig config, final Invoice invoice, final Locale locale,
+                                   final CurrencyConversionApi currencyConversionApi, final ResourceBundleFactory bundleFactory,
+                                   final InternalTenantContext context, final Map<java.util.Currency, Locale> currencyLocaleMap) {
         this.config = config;
         this.invoice = invoice;
         this.dateFormatter = DateTimeFormat.mediumDate().withLocale(locale);
@@ -79,11 +83,12 @@ public class DefaultInvoiceFormatter implements InvoiceFormatter {
         this.currencyConversionApi = currencyConversionApi;
         this.bundleFactory = bundleFactory;
         this.context = context;
+        this.currencyLocaleMap = currencyLocaleMap;
     }
 
     @Override
     public Integer getInvoiceNumber() {
-        return Objects.firstNonNull(invoice.getInvoiceNumber(), 0);
+        return MoreObjects.firstNonNull(invoice.getInvoiceNumber(), 0);
     }
 
     @Override
@@ -163,7 +168,7 @@ public class DefaultInvoiceFormatter implements InvoiceFormatter {
 
     @Override
     public <T extends InvoiceItem> List<InvoiceItem> getInvoiceItems(final Class<T> clazz) {
-        return Objects.firstNonNull(invoice.getInvoiceItems(clazz), ImmutableList.<InvoiceItem>of());
+        return MoreObjects.firstNonNull(invoice.getInvoiceItems(clazz), ImmutableList.<InvoiceItem>of());
     }
 
     @Override
@@ -183,7 +188,7 @@ public class DefaultInvoiceFormatter implements InvoiceFormatter {
 
     @Override
     public List<InvoicePayment> getPayments() {
-        return Objects.firstNonNull(invoice.getPayments(), ImmutableList.<InvoicePayment>of());
+        return MoreObjects.firstNonNull(invoice.getPayments(), ImmutableList.<InvoicePayment>of());
     }
 
     @Override
@@ -198,35 +203,55 @@ public class DefaultInvoiceFormatter implements InvoiceFormatter {
 
     @Override
     public BigDecimal getChargedAmount() {
-        return round(Objects.firstNonNull(invoice.getChargedAmount(), BigDecimal.ZERO));
+        return MoreObjects.firstNonNull(invoice.getChargedAmount(), BigDecimal.ZERO);
     }
 
     @Override
     public BigDecimal getOriginalChargedAmount() {
-        return round(Objects.firstNonNull(invoice.getOriginalChargedAmount(), BigDecimal.ZERO));
+        return MoreObjects.firstNonNull(invoice.getOriginalChargedAmount(), BigDecimal.ZERO);
     }
 
     @Override
     public BigDecimal getBalance() {
-        return round(Objects.firstNonNull(invoice.getBalance(), BigDecimal.ZERO));
+        return MoreObjects.firstNonNull(invoice.getBalance(), BigDecimal.ZERO);
     }
 
     @Override
     public String getFormattedChargedAmount() {
-        final NumberFormat number = NumberFormat.getCurrencyInstance(locale);
-        return number.format(getChargedAmount().doubleValue());
+        return getFormattedAmountByLocaleAndInvoiceCurrency(getChargedAmount());
     }
 
     @Override
     public String getFormattedPaidAmount() {
-        final NumberFormat number = NumberFormat.getCurrencyInstance(locale);
-        return number.format(getPaidAmount().doubleValue());
+        return getFormattedAmountByLocaleAndInvoiceCurrency(getPaidAmount());
     }
 
     @Override
     public String getFormattedBalance() {
-        final NumberFormat number = NumberFormat.getCurrencyInstance(locale);
-        return number.format(getBalance().doubleValue());
+        return getFormattedAmountByLocaleAndInvoiceCurrency(getBalance());
+    }
+
+    // Returns the formatted amount with the correct currency symbol that is get from the invoice currency.
+    private String getFormattedAmountByLocaleAndInvoiceCurrency(final BigDecimal amount) {
+        final String invoiceCurrencyCode = invoice.getCurrency().toString();
+        final CurrencyUnit currencyUnit = CurrencyUnit.of(invoiceCurrencyCode);
+
+        final DecimalFormat numberFormatter = (DecimalFormat) DecimalFormat.getCurrencyInstance(locale);
+        final DecimalFormatSymbols dfs = numberFormatter.getDecimalFormatSymbols();
+        dfs.setInternationalCurrencySymbol(currencyUnit.getCurrencyCode());
+
+        try {
+            final java.util.Currency currency = java.util.Currency.getInstance(invoiceCurrencyCode);
+            dfs.setCurrencySymbol(currency.getSymbol(currencyLocaleMap.get(currency)));
+        } catch (final IllegalArgumentException e) {
+            dfs.setCurrencySymbol(currencyUnit.getSymbol(locale));
+        }
+
+        numberFormatter.setDecimalFormatSymbols(dfs);
+        numberFormatter.setMinimumFractionDigits(currencyUnit.getDefaultFractionDigits());
+        numberFormatter.setMaximumFractionDigits(currencyUnit.getDefaultFractionDigits());
+
+        return numberFormatter.format(amount.doubleValue());
     }
 
     @Override
@@ -244,7 +269,7 @@ public class DefaultInvoiceFormatter implements InvoiceFormatter {
         }
         // If there were multiple payments (and refunds) we pick chose the last one
         DateTime latestPaymentDate = null;
-        final Iterator<InvoicePayment> paymentIterator = ((DefaultInvoice) invoice).getPayments().iterator();
+        final Iterator<InvoicePayment> paymentIterator = invoice.getPayments().iterator();
         while (paymentIterator.hasNext()) {
             final InvoicePayment cur = paymentIterator.next();
             latestPaymentDate = latestPaymentDate != null && latestPaymentDate.isAfter(cur.getPaymentDate()) ?
@@ -253,12 +278,12 @@ public class DefaultInvoiceFormatter implements InvoiceFormatter {
         }
         try {
             final CurrencyConversion conversion = currencyConversionApi.getCurrencyConversion(currency, latestPaymentDate);
-            for (Rate rate : conversion.getRates()) {
+            for (final Rate rate : conversion.getRates()) {
                 if (rate.getCurrency() == getCurrency()) {
                     return rate.getValue().toString();
                 }
             }
-        } catch (CurrencyConversionException e) {
+        } catch (final CurrencyConversionException e) {
             logger.warn("Failed to retrieve currency conversion rates for currency = " + currency + " and date = " + latestPaymentDate, e);
             return null;
         }
@@ -288,7 +313,7 @@ public class DefaultInvoiceFormatter implements InvoiceFormatter {
 
     @Override
     public BigDecimal getPaidAmount() {
-        return round(Objects.firstNonNull(invoice.getPaidAmount(), BigDecimal.ZERO));
+        return MoreObjects.firstNonNull(invoice.getPaidAmount(), BigDecimal.ZERO);
     }
 
     @Override
@@ -339,11 +364,11 @@ public class DefaultInvoiceFormatter implements InvoiceFormatter {
 
     @Override
     public BigDecimal getCreditedAmount() {
-        return round(Objects.firstNonNull(invoice.getCreditedAmount(), BigDecimal.ZERO));
+        return MoreObjects.firstNonNull(invoice.getCreditedAmount(), BigDecimal.ZERO);
     }
 
     @Override
     public BigDecimal getRefundedAmount() {
-        return round(Objects.firstNonNull(invoice.getRefundedAmount(), BigDecimal.ZERO));
+        return MoreObjects.firstNonNull(invoice.getRefundedAmount(), BigDecimal.ZERO);
     }
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatterFactory.java b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatterFactory.java
index 890fb30..b9e405b 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatterFactory.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceFormatterFactory.java
@@ -16,7 +16,10 @@
 
 package org.killbill.billing.invoice.template.formatters;
 
+import java.util.Currency;
+import java.util.HashMap;
 import java.util.Locale;
+import java.util.Map;
 
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.currency.api.CurrencyConversionApi;
@@ -24,14 +27,33 @@ import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.formatters.InvoiceFormatter;
 import org.killbill.billing.invoice.api.formatters.InvoiceFormatterFactory;
 import org.killbill.billing.invoice.api.formatters.ResourceBundleFactory;
-import org.killbill.billing.tenant.api.TenantInternalApi;
 import org.killbill.billing.util.template.translation.TranslatorConfig;
 
+import com.google.common.annotations.VisibleForTesting;
+
 public class DefaultInvoiceFormatterFactory implements InvoiceFormatterFactory {
 
+    private final Map<Currency, Locale> currencyLocaleMap = new HashMap<Currency, Locale>();
+
+    public DefaultInvoiceFormatterFactory() {
+        // This initialization relies on System.currentTimeMillis() instead of the Kill Bill clock (it won't be accurate when moving the clock)
+        for (final Locale localeItem : Locale.getAvailableLocales()) {
+            try {
+                final java.util.Currency currency = java.util.Currency.getInstance(localeItem);
+                currencyLocaleMap.put(currency, localeItem);
+            } catch (final Exception ignored) {
+            }
+        }
+    }
+
     @Override
-    public InvoiceFormatter createInvoiceFormatter(final TranslatorConfig config, final Invoice invoice, final Locale locale, CurrencyConversionApi currencyConversionApi,
+    public InvoiceFormatter createInvoiceFormatter(final TranslatorConfig config, final Invoice invoice, final Locale locale, final CurrencyConversionApi currencyConversionApi,
                                                    final ResourceBundleFactory bundleFactory, final InternalTenantContext context) {
-        return new DefaultInvoiceFormatter(config, invoice, locale, currencyConversionApi, bundleFactory, context);
+        return new DefaultInvoiceFormatter(config, invoice, locale, currencyConversionApi, bundleFactory, context, currencyLocaleMap);
+    }
+
+    @VisibleForTesting
+    Map<Currency, Locale> getCurrencyLocaleMap() {
+        return currencyLocaleMap;
     }
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
index 51ddcc6..5516c8c 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
@@ -36,14 +36,10 @@ import org.killbill.billing.util.LocaleUtils;
 import org.killbill.billing.util.template.translation.DefaultCatalogTranslator;
 import org.killbill.billing.util.template.translation.Translator;
 import org.killbill.billing.util.template.translation.TranslatorConfig;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Objects;
 import com.google.common.base.Strings;
 
-import static org.killbill.billing.util.DefaultAmountFormatter.round;
-
 /**
  * Format invoice item fields
  */
@@ -71,7 +67,7 @@ public class DefaultInvoiceItemFormatter implements InvoiceItemFormatter {
 
     @Override
     public BigDecimal getAmount() {
-        return round(Objects.firstNonNull(item.getAmount(), BigDecimal.ZERO));
+        return Objects.firstNonNull(item.getAmount(), BigDecimal.ZERO);
     }
 
     @Override
@@ -168,7 +164,7 @@ public class DefaultInvoiceItemFormatter implements InvoiceItemFormatter {
 
     @Override
     public BigDecimal getRate() {
-        return round(BigDecimal.ZERO);
+        return BigDecimal.ZERO;
     }
 
     @Override
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java b/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java
index df0a9d7..32b9b86 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -34,7 +34,6 @@ import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoiceItemType;
 import org.killbill.billing.invoice.api.InvoicePaymentType;
 import org.killbill.billing.invoice.api.formatters.InvoiceFormatter;
-import org.killbill.billing.invoice.api.formatters.InvoiceFormatterFactory;
 import org.killbill.billing.invoice.api.formatters.ResourceBundleFactory.ResourceBundleType;
 import org.killbill.billing.invoice.model.CreditAdjInvoiceItem;
 import org.killbill.billing.invoice.model.CreditBalanceAdjInvoiceItem;
@@ -55,12 +54,14 @@ public class TestDefaultInvoiceFormatter extends InvoiceTestSuiteNoDB {
 
     private TranslatorConfig config;
     private MustacheTemplateEngine templateEngine;
+    private DefaultInvoiceFormatterFactory defaultInvoiceFormatterFactory;
 
     @BeforeClass(groups = "fast")
     public void beforeClass() throws Exception {
         super.beforeClass();
         config = new ConfigurationObjectFactory(skifeConfigSource).build(TranslatorConfig.class);
         templateEngine = new MustacheTemplateEngine();
+        defaultInvoiceFormatterFactory = new DefaultInvoiceFormatterFactory();
     }
 
     @Test(groups = "fast")
@@ -89,7 +90,7 @@ public class TestDefaultInvoiceFormatter extends InvoiceTestSuiteNoDB {
         Assert.assertEquals(invoice.getCreditedAmount().doubleValue(), 0.00);
 
         // Verify the merge
-        final InvoiceFormatter formatter = new DefaultInvoiceFormatter(config, invoice, Locale.US, null, resourceBundleFactory, internalCallContext);
+        final InvoiceFormatter formatter = new DefaultInvoiceFormatter(config, invoice, Locale.US, null, resourceBundleFactory, internalCallContext, defaultInvoiceFormatterFactory.getCurrencyLocaleMap());
         final List<InvoiceItem> invoiceItems = formatter.getInvoiceItems();
         Assert.assertEquals(invoiceItems.size(), 1);
         Assert.assertEquals(invoiceItems.get(0).getInvoiceItemType(), InvoiceItemType.FIXED);
@@ -143,7 +144,7 @@ public class TestDefaultInvoiceFormatter extends InvoiceTestSuiteNoDB {
         Assert.assertEquals(invoice.getRefundedAmount().doubleValue(), -1.00);
 
         // Verify the merge
-        final InvoiceFormatter formatter = new DefaultInvoiceFormatter(config, invoice, Locale.US, null, resourceBundleFactory, internalCallContext);
+        final InvoiceFormatter formatter = new DefaultInvoiceFormatter(config, invoice, Locale.US, null, resourceBundleFactory, internalCallContext, defaultInvoiceFormatterFactory.getCurrencyLocaleMap());
         final List<InvoiceItem> invoiceItems = formatter.getInvoiceItems();
         Assert.assertEquals(invoiceItems.size(), 4);
         Assert.assertEquals(invoiceItems.get(0).getInvoiceItemType(), InvoiceItemType.FIXED);
@@ -157,7 +158,7 @@ public class TestDefaultInvoiceFormatter extends InvoiceTestSuiteNoDB {
     }
 
     @Test(groups = "fast")
-    public void testFormattedAmount() throws Exception {
+    public void testFormattedAmountFranceAndEUR() 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);
@@ -187,6 +188,186 @@ public class TestDefaultInvoiceFormatter extends InvoiceTestSuiteNoDB {
     }
 
     @Test(groups = "fast")
+    public void testFormattedAmountFranceAndOMR() throws Exception {
+        final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
+                                                                          UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                          new LocalDate(), new BigDecimal("1499.958"), Currency.OMR);
+        final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.OMR);
+        invoice.addInvoiceItem(fixedItem);
+
+        checkOutput(invoice,
+                    "<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,958 ر.ع.\u200F</strong></td>\n" +
+                    "</tr>\n" +
+                    "<tr>\n" +
+                    "    <td class=\"amount\"><strong>0,000 ر.ع.\u200F</strong></td>\n" +
+                    "</tr>\n" +
+                    "<tr>\n" +
+                    "    <td class=\"amount\"><strong>1 499,958 ر.ع.\u200F</strong></td>\n" +
+                    "</tr>",
+                    Locale.FRANCE);
+    }
+
+    @Test(groups = "fast")
+    public void testFormattedAmountFranceAndJPY() throws Exception {
+        final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
+                                                                          UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                          new LocalDate(), new BigDecimal("1500.00"), Currency.JPY);
+        final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.JPY);
+        invoice.addInvoiceItem(fixedItem);
+
+        checkOutput(invoice,
+                    "<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 500 ¥</strong></td>\n" +
+                    "</tr>\n" +
+                    "<tr>\n" +
+                    "    <td class=\"amount\"><strong>0 ¥</strong></td>\n" +
+                    "</tr>\n" +
+                    "<tr>\n" +
+                    "    <td class=\"amount\"><strong>1 500 ¥</strong></td>\n" +
+                    "</tr>",
+                    Locale.FRANCE);
+    }
+
+    @Test(groups = "fast")
+    public void testFormattedAmountUSAndBTC() throws Exception {
+        final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
+                                                                          UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                          new LocalDate(), new BigDecimal("1105.28843439"), Currency.BTC);
+        final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.BTC);
+        invoice.addInvoiceItem(fixedItem);
+
+        checkOutput(invoice,
+                    "<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>BTC1,105.28843439</strong></td>\n" +
+                    "</tr>\n" +
+                    "<tr>\n" +
+                    "    <td class=\"amount\"><strong>BTC0.00000000</strong></td>\n" +
+                    "</tr>\n" +
+                    "<tr>\n" +
+                    "    <td class=\"amount\"><strong>BTC1,105.28843439</strong></td>\n" +
+                    "</tr>",
+                    Locale.US);
+    }
+
+    @Test(groups = "fast")
+    public void testFormattedAmountUSAndEUR() throws Exception {
+        final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
+                                                                          UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                          new LocalDate(), new BigDecimal("2635.14"), Currency.EUR);
+        final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.EUR);
+        invoice.addInvoiceItem(fixedItem);
+
+        checkOutput(invoice,
+                    "<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>€2,635.14</strong></td>\n" +
+                    "</tr>\n" +
+                    "<tr>\n" +
+                    "    <td class=\"amount\"><strong>€0.00</strong></td>\n" +
+                    "</tr>\n" +
+                    "<tr>\n" +
+                    "    <td class=\"amount\"><strong>€2,635.14</strong></td>\n" +
+                    "</tr>",
+                    Locale.US);
+    }
+
+    @Test(groups = "fast")
+    public void testFormattedAmountUSAndBRL() throws Exception {
+        final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
+                                                                          UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                          new LocalDate(), new BigDecimal("2635.14"), Currency.BRL);
+        final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.BRL);
+        invoice.addInvoiceItem(fixedItem);
+
+        checkOutput(invoice,
+                    "<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>R$2,635.14</strong></td>\n" +
+                    "</tr>\n" +
+                    "<tr>\n" +
+                    "    <td class=\"amount\"><strong>R$0.00</strong></td>\n" +
+                    "</tr>\n" +
+                    "<tr>\n" +
+                    "    <td class=\"amount\"><strong>R$2,635.14</strong></td>\n" +
+                    "</tr>",
+                    Locale.US);
+    }
+
+    @Test(groups = "fast")
+    public void testFormattedAmountUSAndGBP() throws Exception {
+        final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
+                                                                          UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                          new LocalDate(), new BigDecimal("1499.95"), Currency.GBP);
+        final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.GBP);
+        invoice.addInvoiceItem(fixedItem);
+
+        checkOutput(invoice,
+                    "<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.US);
+    }
+
+    @Test(groups = "fast")
     public void testProcessedCurrencyExists() throws Exception {
         final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), clock.getUTCNow(), UUID.randomUUID(), new Integer(234), new LocalDate(), new LocalDate(), Currency.BRL, Currency.USD, false);
 
@@ -329,7 +510,7 @@ public class TestDefaultInvoiceFormatter extends InvoiceTestSuiteNoDB {
 
         data.put("text", translator);
 
-        data.put("invoice", new DefaultInvoiceFormatter(config, invoice, Locale.US, currencyConversionApi, resourceBundleFactory, internalCallContext));
+        data.put("invoice", new DefaultInvoiceFormatter(config, invoice, Locale.US, currencyConversionApi, resourceBundleFactory, internalCallContext, defaultInvoiceFormatterFactory.getCurrencyLocaleMap()));
 
         final String formattedText = templateEngine.executeTemplateText(template, data);
 
@@ -338,7 +519,7 @@ public class TestDefaultInvoiceFormatter extends InvoiceTestSuiteNoDB {
 
     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, null, resourceBundleFactory, internalCallContext));
+        data.put("invoice", new DefaultInvoiceFormatter(config, invoice, locale, null, resourceBundleFactory, internalCallContext, defaultInvoiceFormatterFactory.getCurrencyLocaleMap()));
 
         final String formattedText = templateEngine.executeTemplateText(template, data);
         Assert.assertEquals(formattedText, expected);