[Payment Request] Update the CurrencyStringFormatter to call the native impl.

Uses components/payments/currency_formatter.h by way of a JNI bridge.

Had to get rid of the junit tests because the implementation now require the
native libraries to be loaded.

BUG=679797
TEST=CurrencyStringFormatterTest, manual

Review-Url: https://codereview.chromium.org/2629883004
Cr-Commit-Position: refs/heads/master@{#443797}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/CurrencyFormatter.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/CurrencyFormatter.java
new file mode 100644
index 0000000..e3b9291
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/CurrencyFormatter.java
@@ -0,0 +1,79 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.payments;
+
+import android.support.annotation.Nullable;
+
+import org.chromium.base.annotations.JNINamespace;
+
+import java.util.Locale;
+
+/**
+ * Formatter for currency amounts.
+ * https://w3c.github.io/browser-payment-api/specs/paymentrequest.html#currencyamount
+ */
+@JNINamespace("payments")
+public class CurrencyFormatter {
+    /**
+     * Pointer to the native implementation.
+     */
+    private long mCurrencyFormatterAndroid;
+
+    /**
+     * Builds the formatter for the given currency code and the current user locale.
+     *
+     * @param currencyCode  The currency code. Most commonly, this follows ISO 4217 format: 3 upper
+     *                      case ASCII letters. For example, "USD". Format is not restricted. Should
+     *                      not be null.
+     * @param currencySystem URI specifying the ISO4217 currency code specification. See for
+     *          details: https://w3c.github.io/browser-payment-api/#paymentcurrencyamount-dictionary
+     *          Can be null, in which case "urn:iso:std:iso:4217" is assumed.
+     * @param userLocale User's current locale. Should not be null.
+     */
+    public CurrencyFormatter(
+            String currencyCode, @Nullable String currencySystem, Locale userLocale) {
+        assert currencyCode != null : "currencyCode should not be null";
+        assert userLocale != null : "userLocale should not be null";
+
+        // Note that this technically leaks the native object.
+        mCurrencyFormatterAndroid = nativeInitCurrencyFormatterAndroid(
+                currencyCode, currencySystem == null ? "" : currencySystem, userLocale.toString());
+    }
+
+    /** Will destroy the native object. This class shouldn't be used afterwards. */
+    public void destroy() {
+        if (mCurrencyFormatterAndroid != 0) {
+            nativeDestroy(mCurrencyFormatterAndroid);
+            mCurrencyFormatterAndroid = 0;
+        }
+    }
+
+    /** @return The currency code formatted for display. */
+    public String getFormattedCurrencyCode() {
+        return nativeGetFormattedCurrencyCode(mCurrencyFormatterAndroid);
+    }
+
+    /**
+     * Formats the currency string for display. Does not parse the string into a number, because it
+     * might be too large. The number is formatted for the current locale and can include a
+     * currency symbol (e.g. $) anywhere in the string, but will not contain the currency code
+     * (e.g. USD/US). All spaces in the currency are unicode non-breaking space.
+     *
+     * @param amountValue The number to format. Should be in "^-?[0-9]+(\.[0-9]+)?$" format. Should
+     *                    not be null.
+     * @return The amount formatted with the specified currency. See description for details.
+     */
+    public String format(String amountValue) {
+        assert amountValue != null : "amountValue should not be null";
+
+        return nativeFormat(mCurrencyFormatterAndroid, amountValue);
+    }
+
+    private native long nativeInitCurrencyFormatterAndroid(
+            String currencyCode, String currencySystem, String localeName);
+    private native void nativeDestroy(long nativeCurrencyFormatterAndroid);
+    private native String nativeFormat(long nativeCurrencyFormatterAndroid, String amountValue);
+    private native String nativeGetFormattedCurrencyCode(long nativeCurrencyFormatterAndroid);
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/CurrencyStringFormatter.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/CurrencyStringFormatter.java
deleted file mode 100644
index 693bc03..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/CurrencyStringFormatter.java
+++ /dev/null
@@ -1,188 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.payments;
-
-import java.text.DecimalFormatSymbols;
-import java.util.Currency;
-import java.util.Locale;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Formatter for currency strings that can be too large to parse into numbers.
- * https://w3c.github.io/browser-payment-api/specs/paymentrequest.html#currencyamount
- */
-public class CurrencyStringFormatter {
-    // Amount value pattern and capture group numbers.
-    private static final String AMOUNT_VALUE_PATTERN = "^(-?)([0-9]+)(\\.([0-9]+))?$";
-    private static final int OPTIONAL_NEGATIVE_GROUP = 1;
-    private static final int DIGITS_BETWEEN_NEGATIVE_AND_PERIOD_GROUP = 2;
-    private static final int DIGITS_AFTER_PERIOD_GROUP = 4;
-
-    // Max currency code length. Maximum length of currency code can be at most 2048.
-    private static final int MAX_CURRENCY_CODE_LEN = 2048;
-
-    // Currency code exceeding 6 chars will be ellipsized during formatting for display.
-    private static final int MAX_CURRENCY_CHARS = 6;
-
-    // Unicode character for ellipsis.
-    private static final String ELLIPSIS = "\u2026";
-
-    // Formatting constants.
-    private static final int DIGIT_GROUPING_SIZE = 3;
-
-    private final Pattern mAmountValuePattern;
-
-    /**
-     * The currency formatted for display. Currency can be any string of at most
-     * 2048 characters.Currency code more than 6 character is formatted to first
-     * 5 characters and ellipsis.
-     */
-    public final String mFormattedCurrencyCode;
-
-    /**
-     * The symbol for the currency specified on the bill. For example, the symbol for "USD" is "$".
-     */
-    private final String mCurrencySymbol;
-
-    /**
-     * The number of digits after the decimal separator for the currency specified on the bill. For
-     * example, 2 for "USD" and 0 for "JPY".
-     */
-    private final int mDefaultFractionDigits;
-
-    /**
-     * The number grouping separator for the current locale. For example, "," in US. 3-digit groups
-     * are assumed.
-     */
-    private final char mGroupingSeparator;
-
-    /**
-     * The monetary decimal separator for the current locale. For example, "." in US and "," in
-     * France.
-     */
-    private final char mMonetaryDecimalSeparator;
-
-    /**
-     * Builds the formatter for the given currency code and the current user locale.
-     *
-     * @param currencyCode The currency code. Most commonly, this follows ISO 4217 format: 3 upper
-     *                     case ASCII letters. For example, "USD". Format is not restricted. Should
-     *                     not be null.
-     * @param userLocale User's current locale. Should not be null.
-     */
-    public CurrencyStringFormatter(String currencyCode, Locale userLocale) {
-        assert currencyCode != null : "currencyCode should not be null";
-        assert userLocale != null : "userLocale should not be null";
-
-        mAmountValuePattern = Pattern.compile(AMOUNT_VALUE_PATTERN);
-
-        mFormattedCurrencyCode = currencyCode.length() <= MAX_CURRENCY_CHARS
-                ? currencyCode
-                : currencyCode.substring(0, MAX_CURRENCY_CHARS - 1) + ELLIPSIS;
-
-        String currencySymbol;
-        int defaultFractionDigits;
-        try {
-            Currency currency = Currency.getInstance(currencyCode);
-            currencySymbol = currency.getSymbol();
-            defaultFractionDigits = currency.getDefaultFractionDigits();
-        } catch (IllegalArgumentException e) {
-            // The spec does not limit the currencies to official ISO 4217 currency code list, which
-            // is used by java.util.Currency. For example, "BTX" (bitcoin) is not an official ISO
-            // 4217 currency code, but is allowed by the spec.
-            currencySymbol = "";
-            defaultFractionDigits = 0;
-        }
-
-        // If the prefix of the currency symbol matches the prefix of the currency code, remove the
-        // matching prefix from the symbol. The UI already shows the currency code, so there's no
-        // need to show duplicate information.
-        String symbol = "";
-        for (int i = 0; i < currencySymbol.length(); i++) {
-            if (i >= currencyCode.length() || currencySymbol.charAt(i) != currencyCode.charAt(i)) {
-                symbol = currencySymbol.substring(i);
-                break;
-            }
-        }
-        mCurrencySymbol = symbol;
-
-        mDefaultFractionDigits = defaultFractionDigits;
-
-        // Use the symbols from user's current locale. For example, use "," for decimal separator in
-        // France, even if paying in "USD".
-        DecimalFormatSymbols symbols = new DecimalFormatSymbols(userLocale);
-        mGroupingSeparator = symbols.getGroupingSeparator();
-        mMonetaryDecimalSeparator = symbols.getMonetaryDecimalSeparator();
-    }
-
-    /**
-     * Returns true if the amount value string is in valid format.
-     *
-     * @param amountValue The number to check for validity.
-     * @return Whether the number is in valid format.
-     */
-    public boolean isValidAmountValue(String amountValue) {
-        return amountValue != null && mAmountValuePattern.matcher(amountValue).matches();
-    }
-
-    /**
-     * Returns true if the currency code string is in valid format.
-     *
-     * @param amountCurrencyCode The currency code to check for validity.
-     * @return Whether the currency code is in valid format.
-     */
-    public boolean isValidAmountCurrencyCode(String amountCurrencyCode) {
-        return amountCurrencyCode != null && amountCurrencyCode.length() <= MAX_CURRENCY_CODE_LEN;
-    }
-
-    /** @return The currency code formatted for display. */
-    public String getFormattedCurrencyCode() {
-        return mFormattedCurrencyCode;
-    }
-
-    /**
-     * Formats the currency string for display. Does not parse the string into a number, because it
-     * might be too large. The number is formatted for the current locale and follows the symbol of
-     * the currency code.
-     *
-     * @param amountValue The number to format. Should be in "^-?[0-9]+(\.[0-9]+)?$" format. Should
-     *                    not be null.
-     * @return The currency symbol followed by a space and the formatted number.
-     */
-    public String format(String amountValue) {
-        assert amountValue != null : "amountValue should not be null";
-
-        Matcher m = mAmountValuePattern.matcher(amountValue);
-
-        // Required to capture the groups.
-        boolean matches = m.matches();
-        assert matches;
-
-        StringBuilder result = new StringBuilder(m.group(OPTIONAL_NEGATIVE_GROUP));
-        result.append(mCurrencySymbol);
-        int digitStart = result.length();
-
-        result.append(m.group(DIGITS_BETWEEN_NEGATIVE_AND_PERIOD_GROUP));
-        for (int i = result.length() - DIGIT_GROUPING_SIZE; i > digitStart;
-                i -= DIGIT_GROUPING_SIZE) {
-            result.insert(i, mGroupingSeparator);
-        }
-
-        String decimals = m.group(DIGITS_AFTER_PERIOD_GROUP);
-        int numberOfDecimals = decimals == null ? 0 : decimals.length();
-
-        if (numberOfDecimals > 0 || mDefaultFractionDigits > 0) {
-            result.append(mMonetaryDecimalSeparator);
-            if (null != decimals) result.append(decimals);
-
-            for (int i = numberOfDecimals; i < mDefaultFractionDigits; i++) {
-                result.append("0");
-            }
-        }
-
-        return result.toString();
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
index 0070b15e..15e0acb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
@@ -266,7 +266,7 @@
     private ContactEditor mContactEditor;
     private boolean mHasRecordedAbortReason;
     private boolean mQueriedCanMakePayment;
-    private CurrencyStringFormatter mFormatter;
+    private CurrencyFormatter mCurrencyFormatter;
 
     /** True if any of the requested payment methods are supported. */
     private boolean mArePaymentMethodsSupported;
@@ -326,6 +326,14 @@
         recordSuccessFunnelHistograms("Initiated");
     }
 
+    protected void finalize() throws Throwable {
+        super.finalize();
+        if (mCurrencyFormatter != null) {
+            // Ensures the native implementation of currency formatter does not leak.
+            mCurrencyFormatter.destroy();
+        }
+    }
+
     /**
      * Called by the merchant website to initialize the payment request data.
      */
@@ -595,23 +603,23 @@
             return false;
         }
 
-        if (mFormatter == null) {
-            mFormatter = new CurrencyStringFormatter(details.total.amount.currency,
-                    Locale.getDefault());
+        if (mCurrencyFormatter == null) {
+            mCurrencyFormatter = new CurrencyFormatter(details.total.amount.currency,
+                    details.total.amount.currencySystem, Locale.getDefault());
         }
 
         // Total is never pending.
-        LineItem uiTotal = new LineItem(
-                details.total.label, mFormatter.getFormattedCurrencyCode(),
-                mFormatter.format(details.total.amount.value), /* isPending */ false);
+        LineItem uiTotal = new LineItem(details.total.label,
+                mCurrencyFormatter.getFormattedCurrencyCode(),
+                mCurrencyFormatter.format(details.total.amount.value), /* isPending */ false);
 
-        List<LineItem> uiLineItems = getLineItems(details.displayItems, mFormatter);
+        List<LineItem> uiLineItems = getLineItems(details.displayItems, mCurrencyFormatter);
 
         mUiShoppingCart = new ShoppingCart(uiTotal, uiLineItems);
         mRawTotal = details.total;
         mRawLineItems = Collections.unmodifiableList(Arrays.asList(details.displayItems));
 
-        mUiShippingOptions = getShippingOptions(details.shippingOptions, mFormatter);
+        mUiShippingOptions = getShippingOptions(details.shippingOptions, mCurrencyFormatter);
 
         for (int i = 0; i < details.modifiers.length; i++) {
             PaymentDetailsModifier modifier = details.modifiers[i];
@@ -636,8 +644,9 @@
         for (int i = 0; i < mPaymentMethodsSection.getSize(); i++) {
             PaymentInstrument instrument = (PaymentInstrument) mPaymentMethodsSection.getItem(i);
             PaymentDetailsModifier modifier = getModifier(instrument);
-            instrument.setModifiedTotal(modifier == null || modifier.total == null ? null
-                    : mFormatter.format(modifier.total.amount.value));
+            instrument.setModifiedTotal(modifier == null || modifier.total == null
+                            ? null
+                            : mCurrencyFormatter.format(modifier.total.amount.value));
         }
 
         updateOrderSummary((PaymentInstrument) mPaymentMethodsSection.getSelectedItem());
@@ -651,10 +660,12 @@
         PaymentItem total = modifier == null ? null : modifier.total;
         if (total == null) total = mRawTotal;
 
-        mUiShoppingCart.setTotal(new LineItem(total.label, mFormatter.getFormattedCurrencyCode(),
-                mFormatter.format(total.amount.value), false /* isPending */));
-        mUiShoppingCart.setAdditionalContents(modifier == null ? null
-                : getLineItems(modifier.additionalDisplayItems, mFormatter));
+        mUiShoppingCart.setTotal(
+                new LineItem(total.label, mCurrencyFormatter.getFormattedCurrencyCode(),
+                        mCurrencyFormatter.format(total.amount.value), false /* isPending */));
+        mUiShoppingCart.setAdditionalContents(modifier == null
+                        ? null
+                        : getLineItems(modifier.additionalDisplayItems, mCurrencyFormatter));
         mUI.updateOrderSummarySection(mUiShoppingCart);
     }
 
@@ -674,7 +685,7 @@
      * @return A list of valid line items.
      */
     private static List<LineItem> getLineItems(
-            @Nullable PaymentItem[] items, CurrencyStringFormatter formatter) {
+            @Nullable PaymentItem[] items, CurrencyFormatter formatter) {
         // Line items are optional.
         if (items == null) return new ArrayList<>();
 
@@ -697,7 +708,7 @@
      * @return The UI representation of the shipping options.
      */
     private static SectionInformation getShippingOptions(
-            @Nullable PaymentShippingOption[] options, CurrencyStringFormatter formatter) {
+            @Nullable PaymentShippingOption[] options, CurrencyFormatter formatter) {
         // Shipping options are optional.
         if (options == null || options.length == 0) {
             return new SectionInformation(PaymentRequestUI.TYPE_SHIPPING_OPTIONS);
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index 9d71001..4ea2d7e 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -692,7 +692,7 @@
   "java/src/org/chromium/chrome/browser/payments/AutofillPaymentInstrument.java",
   "java/src/org/chromium/chrome/browser/payments/CardEditor.java",
   "java/src/org/chromium/chrome/browser/payments/ContactEditor.java",
-  "java/src/org/chromium/chrome/browser/payments/CurrencyStringFormatter.java",
+  "java/src/org/chromium/chrome/browser/payments/CurrencyFormatter.java",
   "java/src/org/chromium/chrome/browser/payments/EditorBase.java",
   "java/src/org/chromium/chrome/browser/payments/PaymentApp.java",
   "java/src/org/chromium/chrome/browser/payments/PaymentAppFactory.java",
@@ -1339,7 +1339,7 @@
   "javatests/src/org/chromium/chrome/browser/physicalweb/UrlInfoTest.java",
   "javatests/src/org/chromium/chrome/browser/physicalweb/UrlManagerTest.java",
   "javatests/src/org/chromium/chrome/browser/policy/CombinedPolicyProviderTest.java",
-  "javatests/src/org/chromium/chrome/browser/payments/CurrencyStringFormatterTest.java",
+  "javatests/src/org/chromium/chrome/browser/payments/CurrencyFormatterTest.java",
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestAbortTest.java",
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestBasicCardTest.java",
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCanMakePaymentQueryNoCardTest.java",
@@ -1536,7 +1536,6 @@
   "junit/src/org/chromium/chrome/browser/omaha/VersionNumberTest.java",
   "junit/src/org/chromium/chrome/browser/payments/AutofillContactTest.java",
   "junit/src/org/chromium/chrome/browser/payments/AutofillContactUnitTest.java",
-  "junit/src/org/chromium/chrome/browser/payments/CurrencyStringFormatterUnitTest.java",
   "junit/src/org/chromium/chrome/browser/snackbar/SnackbarCollectionUnitTest.java",
   "junit/src/org/chromium/chrome/browser/superviseduser/SupervisedUserContentProviderUnitTest.java",
   "junit/src/org/chromium/chrome/browser/tabstate/TabStateUnitTest.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/CurrencyFormatterTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/CurrencyFormatterTest.java
new file mode 100644
index 0000000..4853fdd1
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/CurrencyFormatterTest.java
@@ -0,0 +1,113 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.payments;
+
+import android.support.test.filters.MediumTest;
+
+import junit.framework.Assert;
+
+import org.chromium.base.LocaleUtils;
+import org.chromium.content.browser.test.NativeLibraryTestBase;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A lightweight integration test for CurrencyFormatter to run on an Android device.
+ */
+public class CurrencyFormatterTest extends NativeLibraryTestBase {
+    /**
+     * Unicode non-breaking space.
+     */
+    private static final String SPACE = "\u00A0";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        loadNativeLibraryAndInitBrowserProcess();
+    }
+
+    private static String longStringOfLength(int len) {
+        StringBuilder currency = new StringBuilder();
+        for (int i = 0; i < len; i++) {
+            currency.append("A");
+        }
+        return currency.toString();
+    }
+
+    @MediumTest
+    public void testMultipleConversions() throws Exception {
+        // Note, all spaces are expected to be unicode non-breaking spaces. Here they are shown as
+        // normal spaces.
+        List<Object[]> testCases = Arrays.asList(new Object[][] {
+                {"55.00", "USD", "en-US", "USD", "$55.00"},
+                {"55.00", "USD", "en-CA", "USD", "$55.00"},
+                {"55.00", "USD", "fr-CA", "USD", "55,00 $"},
+                {"55.00", "USD", "fr-FR", "USD", "55,00 $"},
+                {"1234", "USD", "fr-FR", "USD", "1 234,00 $"},
+
+                {"55.5", "USD", "en-US", "USD", "$55.50"}, {"55", "USD", "en-US", "USD", "$55.00"},
+                {"-123", "USD", "en-US", "USD", "-$123.00"},
+                {"-1234", "USD", "en-US", "USD", "-$1,234.00"},
+                {"0.1234", "USD", "en-US", "USD", "$0.1234"},
+
+                {"55.00", "EUR", "en-US", "EUR", "€55.00"},
+                {"55.00", "EUR", "en-CA", "EUR", "€55.00"},
+                {"55.00", "EUR", "fr-CA", "EUR", "55,00 €"},
+                {"55.00", "EUR", "fr-FR", "EUR", "55,00 €"},
+
+                {"55.00", "CAD", "en-US", "CAD", "$55.00"},
+                {"55.00", "CAD", "en-CA", "CAD", "$55.00"},
+                {"55.00", "CAD", "fr-CA", "CAD", "55,00 $"},
+                {"55.00", "CAD", "fr-FR", "CAD", "55,00 $"},
+
+                {"55", "JPY", "ja-JP", "JPY", "¥55"}, {"55.0", "JPY", "ja-JP", "JPY", "¥55"},
+                {"55.00", "JPY", "ja-JP", "JPY", "¥55"},
+                {"55.12", "JPY", "ja-JP", "JPY", "¥55.12"},
+                {"55.49", "JPY", "ja-JP", "JPY", "¥55.49"},
+                {"55.50", "JPY", "ja-JP", "JPY", "¥55.5"},
+                {"55.9999", "JPY", "ja-JP", "JPY", "¥55.9999"},
+
+                // Unofficial ISO 4217 currency code.
+                {"55.00", "BTX", "en-US", "BTX", "55.00"},
+                {"-0.00000001", "BTX", "en-US", "BTX", "-0.00000001"},
+                {"-55.00", "BTX", "fr-FR", "BTX", "-55,00"},
+
+                {"123456789012345678901234567890.123456789012345678901234567890", "USD", "fr-FR",
+                        "USD", "123 456 789 012 345 678 901 234 567 890,123456789 $"},
+                {"123456789012345678901234567890.123456789012345678901234567890", "USD", "en-US",
+                        "USD", "$123,456,789,012,345,678,901,234,567,890.123456789"},
+
+                // Any string of at most 2048 characters can be valid amount currency codes.
+                {"55.00", "", "en-US", "", "55.00"},
+                {"55.00", "ABCDEF", "en-US", "ABCDE\u2026", "55.00"},
+                // Currency code more than 6 character is formatted to first 5 characters and
+                // ellipsis. "\u2026" is unicode for ellipsis.
+                {"55.00", longStringOfLength(2048), "en-US", "AAAAA\u2026", "55.00"},
+        });
+
+        for (int i = 0; i < testCases.size(); i++) {
+            Object[] testCase = testCases.get(i);
+
+            String amount = (String) testCase[0];
+            String currency = (String) testCase[1];
+            String locale = (String) testCase[2];
+            String expectedCurrencyFormatting = (String) testCase[3];
+            String expectedAmountFormatting = (String) testCase[4];
+
+            CurrencyFormatter formatter =
+                    new CurrencyFormatter(currency, null, LocaleUtils.forLanguageTag(locale));
+
+            String formattedAmount = formatter.format(amount).replace(SPACE, " ");
+            Assert.assertEquals("\"" + currency + "\" \"" + amount + "\" (\"" + locale
+                            + "\" locale) should be formatted into \"" + expectedAmountFormatting
+                            + "\"",
+                    expectedAmountFormatting, formattedAmount);
+            Assert.assertEquals("\"" + currency + "\""
+                            + " should be formatted into \"" + expectedCurrencyFormatting + "\"",
+                    expectedCurrencyFormatting, formatter.getFormattedCurrencyCode());
+        }
+    }
+}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/CurrencyStringFormatterTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/CurrencyStringFormatterTest.java
deleted file mode 100644
index 810a0ae..0000000
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/CurrencyStringFormatterTest.java
+++ /dev/null
@@ -1,109 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.payments;
-
-import android.support.test.filters.SmallTest;
-import android.test.InstrumentationTestCase;
-
-import java.util.Locale;
-
-/**
- * A lightweight integration test for CurrencyStringFormatter to run on an Android device.
- */
-public class CurrencyStringFormatterTest extends InstrumentationTestCase {
-    @SmallTest
-    public void testCad() throws Exception {
-        CurrencyStringFormatter formatter = new CurrencyStringFormatter("CAD", new Locale("en-US"));
-        assertEquals("$5.00", formatter.format("5"));
-    }
-
-    @SmallTest
-    public void testAzn() throws Exception {
-        CurrencyStringFormatter formatter = new CurrencyStringFormatter("AZN", new Locale("en-US"));
-        assertEquals("5.00", formatter.format("5"));
-    }
-
-    @SmallTest
-    public void testBmd() throws Exception {
-        CurrencyStringFormatter formatter = new CurrencyStringFormatter("BMD", new Locale("en-US"));
-        assertEquals("5.00", formatter.format("5"));
-    }
-
-    @SmallTest
-    public void testAud() throws Exception {
-        CurrencyStringFormatter formatter = new CurrencyStringFormatter("AUD", new Locale("en-US"));
-        assertEquals("$5.00", formatter.format("5"));
-    }
-
-    @SmallTest
-    public void testBzd() throws Exception {
-        CurrencyStringFormatter formatter = new CurrencyStringFormatter("BZD", new Locale("en-US"));
-        assertEquals("5.00", formatter.format("5"));
-    }
-
-    @SmallTest
-    public void testClp() throws Exception {
-        CurrencyStringFormatter formatter = new CurrencyStringFormatter("CLP", new Locale("en-US"));
-        assertEquals("5", formatter.format("5"));
-    }
-
-    @SmallTest
-    public void testLrd() throws Exception {
-        CurrencyStringFormatter formatter = new CurrencyStringFormatter("LRD", new Locale("en-US"));
-        assertEquals("5.00", formatter.format("5"));
-    }
-
-    @SmallTest
-    public void testNio() throws Exception {
-        CurrencyStringFormatter formatter = new CurrencyStringFormatter("NIO", new Locale("en-US"));
-        assertEquals("5.00", formatter.format("5"));
-    }
-
-    @SmallTest
-    public void testHrk() throws Exception {
-        CurrencyStringFormatter formatter = new CurrencyStringFormatter("HRK", new Locale("en-US"));
-        assertEquals("5.00", formatter.format("5"));
-    }
-
-    @SmallTest
-    public void testRub() throws Exception {
-        CurrencyStringFormatter formatter = new CurrencyStringFormatter("RUB", new Locale("en-US"));
-        assertEquals("5.00", formatter.format("5"));
-    }
-
-    @SmallTest
-    public void testEur() throws Exception {
-        CurrencyStringFormatter formatter = new CurrencyStringFormatter("EUR", new Locale("en-US"));
-        assertEquals("€5.00", formatter.format("5"));
-    }
-
-    @SmallTest
-    public void testPkr() throws Exception {
-        CurrencyStringFormatter formatter = new CurrencyStringFormatter("PKR", new Locale("en-US"));
-        assertEquals("5", formatter.format("5"));
-    }
-
-    @SmallTest
-    public void testCurrency6Chars() throws Exception {
-        CurrencyStringFormatter formatter =
-                new CurrencyStringFormatter("ABCDEF", new Locale("en-US"));
-        assertEquals("ABCDEF", formatter.getFormattedCurrencyCode());
-    }
-
-    @SmallTest
-    public void testCurrencyEllipsized() throws Exception {
-        CurrencyStringFormatter formatter =
-                new CurrencyStringFormatter("ABCDEFG", new Locale("en-US"));
-        // Currency code more than 6 character is formatted to first 5 characters and ellipsis.
-        // "\u2026" is unicode for ellipsis.
-        assertEquals("ABCDE\u2026", formatter.getFormattedCurrencyCode());
-    }
-
-    @SmallTest
-    public void testCurrencyEmpty() throws Exception {
-        CurrencyStringFormatter formatter = new CurrencyStringFormatter("", new Locale("en-US"));
-        assertEquals("", formatter.getFormattedCurrencyCode());
-    }
-}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/payments/CurrencyStringFormatterUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/payments/CurrencyStringFormatterUnitTest.java
deleted file mode 100644
index 7a5e115b..0000000
--- a/chrome/android/junit/src/org/chromium/chrome/browser/payments/CurrencyStringFormatterUnitTest.java
+++ /dev/null
@@ -1,160 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.payments;
-
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Locale;
-
-/**
- * Unit tests for the CurrencyStringFormatter class.
- */
-@RunWith(Parameterized.class)
-public class CurrencyStringFormatterUnitTest {
-    /**
-     * Unicode non-breaking space.
-     */
-    private static final String SPACE = "\u00A0";
-
-    private enum ExpectedValidity {
-        VALID_AMOUNT,
-        INVALID_AMOUNT_CURRENCY_CODE,
-        INVALID_AMOUNT_VALUE,
-    };
-
-    @Parameters
-    public static Collection<Object[]> data() {
-        return Arrays.asList(new Object[][] {
-            {"55.00", "USD", "en-US", "USD", "$55.00", ExpectedValidity.VALID_AMOUNT},
-            {"55.00", "USD", "en-CA", "USD", "$55.00", ExpectedValidity.VALID_AMOUNT},
-            {"55.00", "USD", "fr-CA", "USD", "$55,00", ExpectedValidity.VALID_AMOUNT},
-            {"55.00", "USD", "fr-FR", "USD", "$55,00", ExpectedValidity.VALID_AMOUNT},
-
-            {"55.00", "EUR", "en-US", "EUR", "55.00", ExpectedValidity.VALID_AMOUNT},
-            {"55.00", "EUR", "en-CA", "EUR", "55.00", ExpectedValidity.VALID_AMOUNT},
-            {"55.00", "EUR", "fr-CA", "EUR", "55,00", ExpectedValidity.VALID_AMOUNT},
-            {"55.00", "EUR", "fr-FR", "EUR", "55,00", ExpectedValidity.VALID_AMOUNT},
-
-            {"55.00", "CAD", "en-US", "CAD", "55.00", ExpectedValidity.VALID_AMOUNT},
-            {"55.00", "CAD", "en-CA", "CAD", "55.00", ExpectedValidity.VALID_AMOUNT},
-            {"55.00", "CAD", "fr-CA", "CAD", "55,00", ExpectedValidity.VALID_AMOUNT},
-            {"55.00", "CAD", "fr-FR", "CAD", "55,00", ExpectedValidity.VALID_AMOUNT},
-
-            {"55.12", "JPY", "ja-JP", "JPY", "55.12", ExpectedValidity.VALID_AMOUNT},
-            {"55.00", "JPY", "ja-JP", "JPY", "55.00", ExpectedValidity.VALID_AMOUNT},
-            {"55.0", "JPY", "ja-JP", "JPY", "55.0", ExpectedValidity.VALID_AMOUNT},
-            {"55", "JPY", "ja-JP", "JPY", "55", ExpectedValidity.VALID_AMOUNT},
-
-            // Unofficial ISO 4217 currency code.
-            {"55.00", "BTX", "en-US", "BTX", "55.00", ExpectedValidity.VALID_AMOUNT},
-            {"-55.00", "BTX", "en-US", "BTX", "-55.00", ExpectedValidity.VALID_AMOUNT},
-
-            {"55.5", "USD", "en-US", "USD", "$55.50", ExpectedValidity.VALID_AMOUNT},
-            {"55", "USD", "en-US", "USD", "$55.00", ExpectedValidity.VALID_AMOUNT},
-            {"123", "USD", "en-US", "USD", "$123.00", ExpectedValidity.VALID_AMOUNT},
-            {"1234", "USD", "en-US", "USD", "$1,234.00", ExpectedValidity.VALID_AMOUNT},
-
-            {"-123", "USD", "en-US", "USD", "-$123.00", ExpectedValidity.VALID_AMOUNT},
-            {"-1234", "USD", "en-US", "USD", "-$1,234.00", ExpectedValidity.VALID_AMOUNT},
-
-            {"123456789012345678901234567890.123456789012345678901234567890", "USD", "fr-FR", "USD",
-                    "$123" + SPACE + "456" + SPACE + "789" + SPACE + "012" + SPACE + "345"
-                            + SPACE + "678" + SPACE + "901" + SPACE + "234" + SPACE + "567"
-                            + SPACE + "890,123456789012345678901234567890",
-                    ExpectedValidity.VALID_AMOUNT},
-            {"123456789012345678901234567890.123456789012345678901234567890", "USD", "en-US", "USD",
-                    "$123,456,789,012,345,678,901,234,567,890.123456789012345678901234567890",
-                    ExpectedValidity.VALID_AMOUNT},
-
-            // Any string of at most 2048 characters can be valid amount currency codes.
-            {"55.00", "", "en-US", "", "55.00", ExpectedValidity.VALID_AMOUNT},
-            {"55.00", "ABCDEF", "en-US", "ABCDEF", "55.00", ExpectedValidity.VALID_AMOUNT},
-            // Currency code more than 6 character is formatted to first 5 characters and ellipsis.
-            // "\u2026" is unicode for ellipsis.
-            {"55.00", longStringOfLength(2048), "en-US", "AAAAA\u2026", "55.00",
-                    ExpectedValidity.VALID_AMOUNT},
-
-            // Invalid amount currency codes.
-            {"55.00", longStringOfLength(2049), "en-US", null, null,
-                    ExpectedValidity.INVALID_AMOUNT_CURRENCY_CODE},
-
-            // Invalid amount values.
-            {"", "USD", "en-US", "USD", null, ExpectedValidity.INVALID_AMOUNT_VALUE},
-            {"-", "USD", "en-US", "USD", null, ExpectedValidity.INVALID_AMOUNT_VALUE},
-            {"notdigits", "USD", "en-US", "USD", null, ExpectedValidity.INVALID_AMOUNT_VALUE},
-            {"ALSONOTDIGITS", "USD", "en-US", "USD", null, ExpectedValidity.INVALID_AMOUNT_VALUE},
-            {"10.", "USD", "en-US", "USD", null, ExpectedValidity.INVALID_AMOUNT_VALUE},
-            {".99", "USD", "en-US", "USD", null, ExpectedValidity.INVALID_AMOUNT_VALUE},
-            {"10-", "USD", "en-US", "USD", null, ExpectedValidity.INVALID_AMOUNT_VALUE},
-            {"1-0", "USD", "en-US", "USD", null, ExpectedValidity.INVALID_AMOUNT_VALUE},
-            {"1.0.0", "USD", "en-US", "USD", null, ExpectedValidity.INVALID_AMOUNT_VALUE},
-            {"1/3", "USD", "en-US", "USD", null, ExpectedValidity.INVALID_AMOUNT_VALUE},
-        });
-    }
-
-    private final String mAmount;
-    private final String mCurrency;
-    private final String mLanguageTag;
-    private final String mExpectedCurrencyFormatting;
-    private final String mExpectedAmountFormatting;
-    private final ExpectedValidity mExpectedValidity;
-
-    private static String longStringOfLength(int len) {
-        StringBuilder currency = new StringBuilder();
-        for (int i = 0; i < len; i++) {
-            currency.append("A");
-        }
-        return currency.toString();
-    }
-
-    public CurrencyStringFormatterUnitTest(String amount, String currency, String languageTag,
-            String expectedCurrencyFormatting, String expectedAmountFormatting,
-            ExpectedValidity expectedValidity) {
-        mAmount = amount;
-        mCurrency = currency;
-        mLanguageTag = languageTag;
-        mExpectedCurrencyFormatting = expectedCurrencyFormatting;
-        mExpectedAmountFormatting = expectedAmountFormatting;
-        mExpectedValidity = expectedValidity;
-    }
-
-    @Test
-    public void test() {
-        CurrencyStringFormatter formatter = new CurrencyStringFormatter(mCurrency,
-                Locale.forLanguageTag(mLanguageTag));
-
-        if (mExpectedValidity == ExpectedValidity.INVALID_AMOUNT_CURRENCY_CODE) {
-            Assert.assertFalse("\"" + mCurrency + "\" should be invalid currency code",
-                    formatter.isValidAmountCurrencyCode(mCurrency));
-        } else {
-            Assert.assertTrue("\"" + mCurrency + "\" should be valid currency code",
-                    formatter.isValidAmountCurrencyCode(mCurrency));
-        }
-
-        if (mExpectedValidity == ExpectedValidity.INVALID_AMOUNT_VALUE) {
-            Assert.assertFalse("\"" + mAmount + "\" should be invalid amount value",
-                    formatter.isValidAmountValue(mAmount));
-        } else {
-            Assert.assertTrue("\"" + mAmount + "\" should be valid amount value",
-                    formatter.isValidAmountValue(mAmount));
-        }
-
-        if (mExpectedValidity == ExpectedValidity.VALID_AMOUNT) {
-            Assert.assertEquals("\"" + mCurrency + "\" \"" + mAmount + "\" (\"" + mLanguageTag
-                            + "\" locale) should be formatted into \""
-                            + mExpectedAmountFormatting + "\"",
-                    mExpectedAmountFormatting, formatter.format(mAmount));
-            Assert.assertEquals("\"" + mCurrency + "\"" + " should be formatted into \""
-                            + mExpectedCurrencyFormatting + "\"",
-                    mExpectedCurrencyFormatting, formatter.getFormattedCurrencyCode());
-        }
-    }
-}
diff --git a/chrome/browser/android/chrome_jni_registrar.cc b/chrome/browser/android/chrome_jni_registrar.cc
index b9f3baa..06161d3 100644
--- a/chrome/browser/android/chrome_jni_registrar.cc
+++ b/chrome/browser/android/chrome_jni_registrar.cc
@@ -168,6 +168,7 @@
 #include "components/gcm_driver/android/component_jni_registrar.h"
 #include "components/gcm_driver/instance_id/android/component_jni_registrar.h"
 #include "components/invalidation/impl/android/component_jni_registrar.h"
+#include "components/payments/android/currency_formatter_android.h"
 #include "components/payments/android/payments_jni_registrar.h"
 #include "components/policy/core/browser/android/component_jni_registrar.h"
 #include "components/safe_browsing_db/android/jni_registrar.h"
@@ -271,6 +272,7 @@
     {"CreditCardScannerBridge",
      autofill::CreditCardScannerViewAndroid::Register},
     {"CtrSuppression", RegisterCtrSuppression},
+    {"CurrencyFormatter", payments::CurrencyFormatterAndroid::Register},
     {"DataReductionPromoInfoBarDelegate",
      DataReductionPromoInfoBarDelegateAndroid::Register},
     {"DataReductionProxySettings", DataReductionProxySettingsAndroid::Register},
diff --git a/components/payments/android/BUILD.gn b/components/payments/android/BUILD.gn
index 01ccece..a1949e5 100644
--- a/components/payments/android/BUILD.gn
+++ b/components/payments/android/BUILD.gn
@@ -7,10 +7,13 @@
 
 static_library("payments_jni") {
   sources = [
+    "currency_formatter_android.cc",
+    "currency_formatter_android.h",
     "payments_jni_registrar.cc",
   ]
   deps = [
     ":jni_headers",
+    "//base",
     "//components/payments:payment_request",
     "//components/payments:payment_validation",
   ]
@@ -18,6 +21,7 @@
 
 generate_jni("jni_headers") {
   sources = [
+    "//chrome/android/java/src/org/chromium/chrome/browser/payments/CurrencyFormatter.java",
     "//chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentValidator.java",
   ]
   jni_package = "payments"
diff --git a/components/payments/android/currency_formatter_android.cc b/components/payments/android/currency_formatter_android.cc
new file mode 100644
index 0000000..00143f3
--- /dev/null
+++ b/components/payments/android/currency_formatter_android.cc
@@ -0,0 +1,76 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/payments/android/currency_formatter_android.h"
+
+#include "base/android/jni_string.h"
+#include "base/optional.h"
+#include "base/strings/string16.h"
+#include "components/payments/currency_formatter.h"
+#include "jni/CurrencyFormatter_jni.h"
+
+using base::android::JavaParamRef;
+using base::android::ConvertJavaStringToUTF8;
+
+namespace payments {
+
+CurrencyFormatterAndroid::CurrencyFormatterAndroid(
+    JNIEnv* env,
+    jobject unused_obj,
+    const JavaParamRef<jstring>& currency_code,
+    const JavaParamRef<jstring>& currency_system,
+    const JavaParamRef<jstring>& locale_name) {
+  std::string currency_system_str =
+      ConvertJavaStringToUTF8(env, currency_system);
+
+  currency_formatter_.reset(new CurrencyFormatter(
+      ConvertJavaStringToUTF8(env, currency_code),
+      currency_system_str.empty()
+          ? base::Optional<std::string>()
+          : base::Optional<std::string>(currency_system_str),
+      ConvertJavaStringToUTF8(env, locale_name)));
+}
+
+CurrencyFormatterAndroid::~CurrencyFormatterAndroid() {}
+
+void CurrencyFormatterAndroid::Destroy(JNIEnv* env,
+                                       const JavaParamRef<jobject>& obj) {
+  delete this;
+}
+
+base::android::ScopedJavaLocalRef<jstring> CurrencyFormatterAndroid::Format(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& unused_obj,
+    const JavaParamRef<jstring>& amount) {
+  base::string16 result =
+      currency_formatter_->Format(ConvertJavaStringToUTF8(env, amount));
+  return base::android::ConvertUTF16ToJavaString(env, result);
+}
+
+base::android::ScopedJavaLocalRef<jstring>
+CurrencyFormatterAndroid::GetFormattedCurrencyCode(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& unused_obj) {
+  return base::android::ConvertUTF8ToJavaString(
+      env, currency_formatter_->formatted_currency_code());
+}
+
+// static
+bool CurrencyFormatterAndroid::Register(JNIEnv* env) {
+  return RegisterNativesImpl(env);
+}
+
+static jlong InitCurrencyFormatterAndroid(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& obj,
+    const JavaParamRef<jstring>& currency_code,
+    const JavaParamRef<jstring>& currency_system,
+    const JavaParamRef<jstring>& locale_name) {
+  CurrencyFormatterAndroid* currency_formatter_android =
+      new CurrencyFormatterAndroid(env, obj, currency_code, currency_system,
+                                   locale_name);
+  return reinterpret_cast<intptr_t>(currency_formatter_android);
+}
+
+}  // namespace payments
diff --git a/components/payments/android/currency_formatter_android.h b/components/payments/android/currency_formatter_android.h
new file mode 100644
index 0000000..f6e6b33e2
--- /dev/null
+++ b/components/payments/android/currency_formatter_android.h
@@ -0,0 +1,51 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PAYMENTS_ANDROID_CURRENCY_FORMATTER_ANDROID_H_
+#define COMPONENTS_PAYMENTS_ANDROID_CURRENCY_FORMATTER_ANDROID_H_
+
+#include <memory>
+
+#include "base/android/scoped_java_ref.h"
+#include "base/macros.h"
+#include "components/payments/currency_formatter.h"
+
+namespace payments {
+
+// Forwarding calls to payments::CurrencyFormatter.
+class CurrencyFormatterAndroid {
+ public:
+  CurrencyFormatterAndroid(
+      JNIEnv* env,
+      jobject unused_obj,
+      const base::android::JavaParamRef<jstring>& currency_code,
+      const base::android::JavaParamRef<jstring>& currency_system,
+      const base::android::JavaParamRef<jstring>& locale_name);
+  ~CurrencyFormatterAndroid();
+
+  // Message from Java to destroy this object.
+  void Destroy(JNIEnv* env, const base::android::JavaParamRef<jobject>& obj);
+
+  // Refer to CurrencyFormatter::Format documentation.
+  base::android::ScopedJavaLocalRef<jstring> Format(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& unused_obj,
+      const base::android::JavaParamRef<jstring>& amount);
+
+  base::android::ScopedJavaLocalRef<jstring> GetFormattedCurrencyCode(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& unused_obj);
+
+  // Registers the JNI bindings for this class.
+  static bool Register(JNIEnv* env);
+
+ private:
+  std::unique_ptr<CurrencyFormatter> currency_formatter_;
+
+  DISALLOW_COPY_AND_ASSIGN(CurrencyFormatterAndroid);
+};
+
+}  // namespace payments
+
+#endif  // COMPONENTS_PAYMENTS_ANDROID_CURRENCY_FORMATTER_ANDROID_H_
diff --git a/components/payments/currency_formatter.cc b/components/payments/currency_formatter.cc
index f333787..d6d4bc0 100644
--- a/components/payments/currency_formatter.cc
+++ b/components/payments/currency_formatter.cc
@@ -17,6 +17,7 @@
 namespace payments {
 
 const char kIso4217CurrencySystem[] = "urn:iso:std:iso:4217";
+
 namespace {
 
 // Support a maximum of 10 fractional digits, similar to the ISO20022 standard.
@@ -27,6 +28,12 @@
 // Max currency code length. Length of currency code can be at most 2048.
 const static size_t kMaxCurrencyCodeLength = 2048;
 
+// Currency codes longer than 6 characters get truncated to 5 + ellipsis.
+const static size_t kMaxCurrencyCodeDisplayedChars = 6;
+
+// Used to truncate long currency codes.
+const char kEllipsis[] = "\xE2\x80\xA6";
+
 // Returns whether the |currency_code| is valid to be used in ICU.
 bool ShouldUseCurrencyCode(const std::string& currency_code,
                            const base::Optional<std::string> currency_system) {
@@ -36,22 +43,27 @@
          currency_code.size() <= kMaxCurrencyCodeLength;
 }
 
+std::string FormatCurrencyCode(const std::string& currency_code) {
+  return currency_code.length() < kMaxCurrencyCodeDisplayedChars
+             ? currency_code
+             : currency_code.substr(0, kMaxCurrencyCodeDisplayedChars - 1) +
+                   kEllipsis;
+}
+
 }  // namespace
 
 CurrencyFormatter::CurrencyFormatter(
     const std::string& currency_code,
     const base::Optional<std::string> currency_system,
     const std::string& locale_name)
-    : locale_(locale_name.c_str()) {
+    : locale_(locale_name.c_str()),
+      formatted_currency_code_(FormatCurrencyCode(currency_code)) {
   UErrorCode error_code = U_ZERO_ERROR;
   icu_formatter_.reset(
       icu::NumberFormat::createCurrencyInstance(locale_, error_code));
   if (U_FAILURE(error_code)) {
-    icu::UnicodeString name;
-    std::string locale_str;
-    locale_.getDisplayName(name).toUTF8String(locale_str);
     LOG(ERROR) << "Failed to initialize the currency formatter for "
-               << locale_str;
+               << locale_name;
     return;
   }
 
diff --git a/components/payments/currency_formatter.h b/components/payments/currency_formatter.h
index 06695a84..2794b37 100644
--- a/components/payments/currency_formatter.h
+++ b/components/payments/currency_formatter.h
@@ -41,9 +41,14 @@
   // formatter, this method will return |amount|.
   base::string16 Format(const std::string& amount);
 
+  // Returns the formatted currency code (<= 6 characters including ellipsis if
+  // applicable).
+  std::string formatted_currency_code() { return formatted_currency_code_; }
+
  private:
   const icu::Locale locale_;
   std::unique_ptr<icu::UnicodeString> currency_code_;
+  std::string formatted_currency_code_;
   std::unique_ptr<icu::NumberFormat> icu_formatter_;
 
   DISALLOW_COPY_AND_ASSIGN(CurrencyFormatter);
diff --git a/components/payments/currency_formatter_unittest.cc b/components/payments/currency_formatter_unittest.cc
index 02e6cfb7..ad679c62 100644
--- a/components/payments/currency_formatter_unittest.cc
+++ b/components/payments/currency_formatter_unittest.cc
@@ -17,11 +17,13 @@
            const char* currency_code,
            const char* locale_name,
            const std::string& expected_amount,
+           const char* expected_currency_code,
            const char* currency_system = kIso4217CurrencySystem)
       : amount(amount),
         currency_code(currency_code),
         locale_name(locale_name),
         expected_amount(expected_amount),
+        expected_currency_code(expected_currency_code),
         currency_system(currency_system) {}
   ~TestCase() {}
 
@@ -29,6 +31,7 @@
   const char* const currency_code;
   const char* const locale_name;
   const std::string expected_amount;
+  const char* const expected_currency_code;
   const base::Optional<std::string> currency_system;
 };
 
@@ -51,82 +54,125 @@
   EXPECT_EQ(converted, output_amount)
       << "Failed to convert " << GetParam().amount << " ("
       << GetParam().currency_code << ") in " << GetParam().locale_name;
+  EXPECT_EQ(GetParam().expected_currency_code,
+            formatter.formatted_currency_code());
 }
 
 INSTANTIATE_TEST_CASE_P(
     CurrencyAmounts,
     PaymentsCurrencyFormatterTest,
     testing::Values(
-        TestCase("55.00", "USD", "en_US", "$55.00"),
-        TestCase("55.00", "USD", "en_CA", "$55.00"),
-        TestCase("55.00", "USD", "fr_CA", "55,00 $"),
-        TestCase("55.00", "USD", "fr_FR", "55,00 $"),
-        TestCase("1234", "USD", "fr_FR", "1 234,00 $"),
+        TestCase("55.00", "USD", "en_US", "$55.00", "USD"),
+        TestCase("55.00", "USD", "en_CA", "$55.00", "USD"),
+        TestCase("55.00", "USD", "fr_CA", "55,00 $", "USD"),
+        TestCase("55.00", "USD", "fr_FR", "55,00 $", "USD"),
+        TestCase("1234", "USD", "fr_FR", "1 234,00 $", "USD"),
 
-        TestCase("55.5", "USD", "en_US", "$55.50"),
-        TestCase("55", "USD", "en_US", "$55.00"),
-        TestCase("123", "USD", "en_US", "$123.00"),
-        TestCase("1234", "USD", "en_US", "$1,234.00"),
-        TestCase("0.1234", "USD", "en_US", "$0.1234"),
+        TestCase("55.5", "USD", "en_US", "$55.50", "USD"),
+        TestCase("55", "USD", "en_US", "$55.00", "USD"),
+        TestCase("123", "USD", "en_US", "$123.00", "USD"),
+        TestCase("1234", "USD", "en_US", "$1,234.00", "USD"),
+        TestCase("0.1234", "USD", "en_US", "$0.1234", "USD"),
 
-        TestCase("55.00", "EUR", "en_US", "€55.00"),
-        TestCase("55.00", "EUR", "fr_CA", "55,00 €"),
-        TestCase("55.00", "EUR", "fr_FR", "55,00 €"),
+        TestCase("55.00", "EUR", "en_US", "€55.00", "EUR"),
+        TestCase("55.00", "EUR", "fr_CA", "55,00 €", "EUR"),
+        TestCase("55.00", "EUR", "fr_FR", "55,00 €", "EUR"),
 
-        TestCase("55.00", "CAD", "en_US", "$55.00"),
-        TestCase("55.00", "CAD", "en_CA", "$55.00"),
-        TestCase("55.00", "CAD", "fr_CA", "55,00 $"),
-        TestCase("55.00", "CAD", "fr_FR", "55,00 $"),
+        TestCase("55.00", "CAD", "en_US", "$55.00", "CAD"),
+        TestCase("55.00", "CAD", "en_CA", "$55.00", "CAD"),
+        TestCase("55.00", "CAD", "fr_CA", "55,00 $", "CAD"),
+        TestCase("55.00", "CAD", "fr_FR", "55,00 $", "CAD"),
 
-        TestCase("55.00", "BRL", "en_US", "R$55.00"),
-        TestCase("55.00", "BRL", "fr_CA", "55,00 R$"),
-        TestCase("55.00", "BRL", "pt_BR", "R$55,00"),
+        TestCase("55.00", "BRL", "en_US", "R$55.00", "BRL"),
+        TestCase("55.00", "BRL", "fr_CA", "55,00 R$", "BRL"),
+        TestCase("55.00", "BRL", "pt_BR", "R$55,00", "BRL"),
 
-        TestCase("55.00", "RUB", "en_US", "55.00"),
-        TestCase("55.00", "RUB", "fr_CA", "55,00"),
-        TestCase("55.00", "RUB", "ru_RU", "55,00 ₽"),
+        TestCase("55.00", "RUB", "en_US", "55.00", "RUB"),
+        TestCase("55.00", "RUB", "fr_CA", "55,00", "RUB"),
+        TestCase("55.00", "RUB", "ru_RU", "55,00 ₽", "RUB"),
 
-        TestCase("55", "JPY", "ja_JP", "¥55"),
-        TestCase("55.0", "JPY", "ja_JP", "¥55"),
-        TestCase("55.00", "JPY", "ja_JP", "¥55"),
-        TestCase("55.12", "JPY", "ja_JP", "¥55.12"),
-        TestCase("55.49", "JPY", "ja_JP", "¥55.49"),
-        TestCase("55.50", "JPY", "ja_JP", "¥55.5"),
-        TestCase("55.9999", "JPY", "ja_JP", "¥55.9999"),
+        TestCase("55", "JPY", "ja_JP", "¥55", "JPY"),
+        TestCase("55.0", "JPY", "ja_JP", "¥55", "JPY"),
+        TestCase("55.00", "JPY", "ja_JP", "¥55", "JPY"),
+        TestCase("55.12", "JPY", "ja_JP", "¥55.12", "JPY"),
+        TestCase("55.49", "JPY", "ja_JP", "¥55.49", "JPY"),
+        TestCase("55.50", "JPY", "ja_JP", "¥55.5", "JPY"),
+        TestCase("55.9999", "JPY", "ja_JP", "¥55.9999", "JPY"),
 
         // Unofficial ISO 4217 currency code.
-        TestCase("55.00", "BTC", "en_US", "55.00"),
-        TestCase("-0.0000000001", "BTC", "en_US", "-0.0000000001"),
-        TestCase("-55.00", "BTC", "fr_FR", "-55,00"),
+        TestCase("55.00", "BTC", "en_US", "55.00", "BTC"),
+        TestCase("-0.0000000001", "BTC", "en_US", "-0.0000000001", "BTC"),
+        TestCase("-55.00", "BTC", "fr_FR", "-55,00", "BTC"),
 
         // Any string of at most 2048 characters can be a valid currency code.
-        TestCase("55.00", "", "en_US", "55.00"),
-        TestCase("55,00", "", "fr_CA", "55,00"),
-        TestCase("55,00", "", "fr-CA", "55,00"),
-        TestCase("55.00", "ABCDEF", "en_US", "55.00"),
+        TestCase("55.00", "", "en_US", "55.00", ""),
+        TestCase("55,00", "", "fr_CA", "55,00", ""),
+        TestCase("55,00", "", "fr-CA", "55,00", ""),
+        TestCase("55.00", "ABCDEF", "en_US", "55.00", "ABCDE\xE2\x80\xA6"),
 
         // Edge cases.
-        TestCase("", "", "", ""),
-        TestCase("-1", "", "", "- 1.00"),
-        TestCase("-1.1255", "", "", "- 1.1255"),
+        TestCase("", "", "", "", ""),
+        TestCase("-1", "", "", "- 1.00", ""),
+        TestCase("-1.1255", "", "", "- 1.1255", ""),
 
         // Handles big numbers.
         TestCase(
             "123456789012345678901234567890.123456789012345678901234567890",
             "USD",
             "fr_FR",
-            "123 456 789 012 345 678 901 234 567 890,123456789 $"),
+            "123 456 789 012 345 678 901 234 567 890,123456789 $",
+            "USD"),
 
         // When the currency system is not ISO4217, only the amount is formatted
         // using the locale (there is no other indication of currency).
-        TestCase("55.00", "USD", "en_CA", "55.00", "http://currsystem.com"),
-        TestCase("55.00", "USD", "fr_CA", "55,00", "http://currsystem.com"),
-        TestCase("55.00", "USD", "fr_FR", "55,00", "http://currsystem.com"),
-        TestCase("1234", "USD", "fr_FR", "1 234,00", "http://currsystem.com"),
-        TestCase("55.5", "USD", "en_US", "55.50", "http://currsystem.com"),
-        TestCase("55", "CAD", "en_US", "55.00", "http://currsystem.com"),
-        TestCase("123", "BTC", "en_US", "123.00", "http://currsystem.com"),
-        TestCase("1234", "JPY", "en_US", "1,234.00", "http://currsystem.com"),
-        TestCase("0.1234", "USD", "en_US", "0.1234", "http://currsystem.com")));
+        TestCase("55.00",
+                 "USD",
+                 "en_CA",
+                 "55.00",
+                 "USD",
+                 "http://currsystem.com"),
+        TestCase("55.00",
+                 "USD",
+                 "fr_CA",
+                 "55,00",
+                 "USD",
+                 "http://currsystem.com"),
+        TestCase("55.00",
+                 "USD",
+                 "fr_FR",
+                 "55,00",
+                 "USD",
+                 "http://currsystem.com"),
+        TestCase("1234",
+                 "USD",
+                 "fr_FR",
+                 "1 234,00",
+                 "USD",
+                 "http://currsystem.com"),
+        TestCase("55.5",
+                 "USD",
+                 "en_US",
+                 "55.50",
+                 "USD",
+                 "http://currsystem.com"),
+        TestCase("55", "CAD", "en_US", "55.00", "CAD", "http://currsystem.com"),
+        TestCase("123",
+                 "BTC",
+                 "en_US",
+                 "123.00",
+                 "BTC",
+                 "http://currsystem.com"),
+        TestCase("1234",
+                 "JPY",
+                 "en_US",
+                 "1,234.00",
+                 "JPY",
+                 "http://currsystem.com"),
+        TestCase("0.1234",
+                 "USD",
+                 "en_US",
+                 "0.1234",
+                 "USD",
+                 "http://currsystem.com")));
 
 }  // namespace payments