Commit 080d5b71 authored by Anthony Vallee-Dubois's avatar Anthony Vallee-Dubois Committed by Commit Bot

[Web Payments] Handle mixed currencies in UI.

Bug: 709296
Change-Id: I79d50b9faaab080f79b95d7da334b500aa37a5b4
Reviewed-on: https://chromium-review.googlesource.com/503490Reviewed-by: default avatarMathieu Perreault <mathp@chromium.org>
Commit-Queue: Mathieu Perreault <mathp@chromium.org>
Cr-Commit-Position: refs/heads/master@{#471179}
parent a08630cb
...@@ -134,6 +134,7 @@ void OrderSummaryViewController::FillContentView(views::View* content_view) { ...@@ -134,6 +134,7 @@ void OrderSummaryViewController::FillContentView(views::View* content_view) {
views::BoxLayout::CROSS_AXIS_ALIGNMENT_STRETCH); views::BoxLayout::CROSS_AXIS_ALIGNMENT_STRETCH);
content_view->SetLayoutManager(layout); content_view->SetLayoutManager(layout);
bool is_mixed_currency = spec()->IsMixedCurrency();
// Set the ID for the first few line items labels, for testing. // Set the ID for the first few line items labels, for testing.
const std::vector<DialogViewID> line_items{ const std::vector<DialogViewID> line_items{
DialogViewID::ORDER_SUMMARY_LINE_ITEM_1, DialogViewID::ORDER_SUMMARY_LINE_ITEM_1,
...@@ -142,20 +143,30 @@ void OrderSummaryViewController::FillContentView(views::View* content_view) { ...@@ -142,20 +143,30 @@ void OrderSummaryViewController::FillContentView(views::View* content_view) {
for (size_t i = 0; i < spec()->details().display_items.size(); i++) { for (size_t i = 0; i < spec()->details().display_items.size(); i++) {
DialogViewID view_id = DialogViewID view_id =
i < line_items.size() ? line_items[i] : DialogViewID::VIEW_ID_NONE; i < line_items.size() ? line_items[i] : DialogViewID::VIEW_ID_NONE;
base::string16 value_string;
if (is_mixed_currency) {
value_string = l10n_util::GetStringFUTF16(
IDS_PAYMENT_REQUEST_ORDER_SUMMARY_SHEET_TOTAL_FORMAT,
base::UTF8ToUTF16(
spec()->details().display_items[i]->amount->currency),
spec()->GetFormattedCurrencyAmount(
spec()->details().display_items[i]->amount));
} else {
value_string = spec()->GetFormattedCurrencyAmount(
spec()->details().display_items[i]->amount);
}
content_view->AddChildView( content_view->AddChildView(
CreateLineItemView( CreateLineItemView(
base::UTF8ToUTF16(spec()->details().display_items[i]->label), base::UTF8ToUTF16(spec()->details().display_items[i]->label),
spec()->GetFormattedCurrencyAmount( value_string, false, view_id)
spec()->details().display_items[i]->amount->value),
false, view_id)
.release()); .release());
} }
base::string16 total_label_value = l10n_util::GetStringFUTF16( base::string16 total_label_value = l10n_util::GetStringFUTF16(
IDS_PAYMENT_REQUEST_ORDER_SUMMARY_SHEET_TOTAL_FORMAT, IDS_PAYMENT_REQUEST_ORDER_SUMMARY_SHEET_TOTAL_FORMAT,
base::UTF8ToUTF16(spec()->details().total->amount->currency), base::UTF8ToUTF16(spec()->details().total->amount->currency),
spec()->GetFormattedCurrencyAmount( spec()->GetFormattedCurrencyAmount(spec()->details().total->amount));
spec()->details().total->amount->value));
content_view->AddChildView( content_view->AddChildView(
CreateLineItemView(base::UTF8ToUTF16(spec()->details().total->label), CreateLineItemView(base::UTF8ToUTF16(spec()->details().total->label),
......
...@@ -576,6 +576,8 @@ PaymentSheetViewController::CreatePaymentSheetSummaryRow() { ...@@ -576,6 +576,8 @@ PaymentSheetViewController::CreatePaymentSheetSummaryRow() {
const std::vector<mojom::PaymentItemPtr>& items = const std::vector<mojom::PaymentItemPtr>& items =
spec()->details().display_items; spec()->details().display_items;
bool is_mixed_currency = spec()->IsMixedCurrency();
// The inline items section contains the first 2 display items of the // The inline items section contains the first 2 display items of the
// request's details, followed by a label indicating "N more items..." if // request's details, followed by a label indicating "N more items..." if
// there are more than 2 items in the details. The total label and amount // there are more than 2 items in the details. The total label and amount
...@@ -588,8 +590,18 @@ PaymentSheetViewController::CreatePaymentSheetSummaryRow() { ...@@ -588,8 +590,18 @@ PaymentSheetViewController::CreatePaymentSheetSummaryRow() {
summary->SetHorizontalAlignment(gfx::ALIGN_LEFT); summary->SetHorizontalAlignment(gfx::ALIGN_LEFT);
layout->AddView(summary.release()); layout->AddView(summary.release());
layout->AddView(new views::Label( base::string16 item_amount;
spec()->GetFormattedCurrencyAmount(items[i]->amount->value))); if (is_mixed_currency) {
// If the payment request has items in different currencies, always
// display the currency code.
item_amount = l10n_util::GetStringFUTF16(
IDS_PAYMENT_REQUEST_ORDER_SUMMARY_SHEET_TOTAL_FORMAT,
base::UTF8ToUTF16(spec()->GetFormattedCurrencyCode(items[i]->amount)),
spec()->GetFormattedCurrencyAmount(items[i]->amount));
} else {
item_amount = spec()->GetFormattedCurrencyAmount(items[i]->amount);
}
layout->AddView(new views::Label(item_amount));
} }
int hidden_item_count = items.size() - kMaxNumberOfItemsShown; int hidden_item_count = items.size() - kMaxNumberOfItemsShown;
...@@ -612,9 +624,10 @@ PaymentSheetViewController::CreatePaymentSheetSummaryRow() { ...@@ -612,9 +624,10 @@ PaymentSheetViewController::CreatePaymentSheetSummaryRow() {
layout->AddView( layout->AddView(
CreateBoldLabel(l10n_util::GetStringFUTF16( CreateBoldLabel(l10n_util::GetStringFUTF16(
IDS_PAYMENT_REQUEST_ORDER_SUMMARY_SHEET_TOTAL_FORMAT, IDS_PAYMENT_REQUEST_ORDER_SUMMARY_SHEET_TOTAL_FORMAT,
base::UTF8ToUTF16(spec()->GetFormattedCurrencyCode()), base::UTF8ToUTF16(spec()->GetFormattedCurrencyCode(
spec()->details().total->amount)),
spec()->GetFormattedCurrencyAmount( spec()->GetFormattedCurrencyAmount(
spec()->details().total->amount->value))) spec()->details().total->amount)))
.release()); .release());
inline_summary->SetLayoutManager(layout.release()); inline_summary->SetLayoutManager(layout.release());
...@@ -887,9 +900,9 @@ PaymentSheetViewController::CreateShippingOptionRow() { ...@@ -887,9 +900,9 @@ PaymentSheetViewController::CreateShippingOptionRow() {
current_update_reason_ == current_update_reason_ ==
PaymentRequestSpec::UpdateReason::SHIPPING_OPTION PaymentRequestSpec::UpdateReason::SHIPPING_OPTION
? CreateCheckingSpinnerView() ? CreateCheckingSpinnerView()
: CreateShippingOptionLabel(selected_option, : CreateShippingOptionLabel(
spec()->GetFormattedCurrencyAmount( selected_option,
selected_option->amount->value), spec()->GetFormattedCurrencyAmount(selected_option->amount),
/*emphasize_label=*/false); /*emphasize_label=*/false);
return builder.Id(DialogViewID::PAYMENT_SHEET_SHIPPING_OPTION_SECTION) return builder.Id(DialogViewID::PAYMENT_SHEET_SHIPPING_OPTION_SECTION)
.CreateWithChevron(std::move(option_row_content), nullptr); .CreateWithChevron(std::move(option_row_content), nullptr);
......
...@@ -33,7 +33,7 @@ class ShippingOptionItem : public PaymentRequestItemList::Item { ...@@ -33,7 +33,7 @@ class ShippingOptionItem : public PaymentRequestItemList::Item {
std::unique_ptr<views::View> CreateContentView() override { std::unique_ptr<views::View> CreateContentView() override {
return CreateShippingOptionLabel( return CreateShippingOptionLabel(
shipping_option_, shipping_option_,
spec()->GetFormattedCurrencyAmount(shipping_option_->amount->value), spec()->GetFormattedCurrencyAmount(shipping_option_->amount),
/*emphasize_label=*/true); /*emphasize_label=*/true);
} }
......
...@@ -115,17 +115,16 @@ bool PaymentRequestSpec::IsMethodSupportedThroughBasicCard( ...@@ -115,17 +115,16 @@ bool PaymentRequestSpec::IsMethodSupportedThroughBasicCard(
} }
base::string16 PaymentRequestSpec::GetFormattedCurrencyAmount( base::string16 PaymentRequestSpec::GetFormattedCurrencyAmount(
const std::string& amount) { const mojom::PaymentCurrencyAmountPtr& currency_amount) {
CurrencyFormatter* formatter = GetOrCreateCurrencyFormatter( CurrencyFormatter* formatter = GetOrCreateCurrencyFormatter(
details_->total->amount->currency, currency_amount->currency, currency_amount->currency_system, app_locale_);
details_->total->amount->currency_system, app_locale_); return formatter->Format(currency_amount->value);
return formatter->Format(amount);
} }
std::string PaymentRequestSpec::GetFormattedCurrencyCode() { std::string PaymentRequestSpec::GetFormattedCurrencyCode(
const mojom::PaymentCurrencyAmountPtr& currency_amount) {
CurrencyFormatter* formatter = GetOrCreateCurrencyFormatter( CurrencyFormatter* formatter = GetOrCreateCurrencyFormatter(
details_->total->amount->currency, currency_amount->currency, currency_amount->currency_system, app_locale_);
details_->total->amount->currency_system, app_locale_);
return formatter->formatted_currency_code(); return formatter->formatted_currency_code();
} }
...@@ -137,6 +136,15 @@ void PaymentRequestSpec::StartWaitingForUpdateWith( ...@@ -137,6 +136,15 @@ void PaymentRequestSpec::StartWaitingForUpdateWith(
} }
} }
bool PaymentRequestSpec::IsMixedCurrency() const {
const std::string& total_currency = details_->total->amount->currency;
return std::any_of(details_->display_items.begin(),
details_->display_items.end(),
[&total_currency](const mojom::PaymentItemPtr& item) {
return item->amount->currency != total_currency;
});
}
void PaymentRequestSpec::PopulateValidatedMethodData( void PaymentRequestSpec::PopulateValidatedMethodData(
const std::vector<mojom::PaymentMethodDataPtr>& method_data_mojom) { const std::vector<mojom::PaymentMethodDataPtr>& method_data_mojom) {
std::vector<PaymentMethodData> method_data_vector; std::vector<PaymentMethodData> method_data_vector;
...@@ -216,11 +224,14 @@ CurrencyFormatter* PaymentRequestSpec::GetOrCreateCurrencyFormatter( ...@@ -216,11 +224,14 @@ CurrencyFormatter* PaymentRequestSpec::GetOrCreateCurrencyFormatter(
const std::string& currency_code, const std::string& currency_code,
const std::string& currency_system, const std::string& currency_system,
const std::string& locale_name) { const std::string& locale_name) {
if (!currency_formatter_) { // Create a currency formatter for |currency_code|, or if already created
currency_formatter_.reset( // return the cached version.
new CurrencyFormatter(currency_code, currency_system, locale_name)); std::pair<std::map<std::string, CurrencyFormatter>::iterator, bool>
} emplace_result = currency_formatters_.emplace(
return currency_formatter_.get(); std::piecewise_construct, std::forward_as_tuple(currency_code),
std::forward_as_tuple(currency_code, currency_system, locale_name));
return &(emplace_result.first->second);
} }
} // namespace payments } // namespace payments
...@@ -87,15 +87,15 @@ class PaymentRequestSpec : public PaymentOptionsProvider { ...@@ -87,15 +87,15 @@ class PaymentRequestSpec : public PaymentOptionsProvider {
// not supported at all, or specified directly in supportedMethods. // not supported at all, or specified directly in supportedMethods.
bool IsMethodSupportedThroughBasicCard(const std::string& method_name); bool IsMethodSupportedThroughBasicCard(const std::string& method_name);
// Uses CurrencyFormatter to format |amount| with the currency symbol for this // Uses CurrencyFormatter to format the value of |currency_amount| with the
// request's currency. Will use currency of the "total" display item, because // currency symbol for its currency.
// all items are supposed to have the same currency in a given request. base::string16 GetFormattedCurrencyAmount(
base::string16 GetFormattedCurrencyAmount(const std::string& amount); const mojom::PaymentCurrencyAmountPtr& currency_amount);
// Uses CurrencyFormatter to get the formatted currency code for this // Uses CurrencyFormatter to get the formatted currency code for
// request's currency. Will use currency of the "total" display item, because // |currency_amount|'s currency.
// all items are supposed to have the same currency in a given request. std::string GetFormattedCurrencyCode(
std::string GetFormattedCurrencyCode(); const mojom::PaymentCurrencyAmountPtr& currency_amount);
mojom::PaymentShippingOption* selected_shipping_option() const { mojom::PaymentShippingOption* selected_shipping_option() const {
return selected_shipping_option_; return selected_shipping_option_;
...@@ -110,6 +110,8 @@ class PaymentRequestSpec : public PaymentOptionsProvider { ...@@ -110,6 +110,8 @@ class PaymentRequestSpec : public PaymentOptionsProvider {
void StartWaitingForUpdateWith(UpdateReason reason); void StartWaitingForUpdateWith(UpdateReason reason);
bool IsMixedCurrency() const;
private: private:
friend class PaymentRequestDialogView; friend class PaymentRequestDialogView;
void add_observer_for_testing(Observer* observer_for_testing) { void add_observer_for_testing(Observer* observer_for_testing) {
...@@ -148,7 +150,8 @@ class PaymentRequestSpec : public PaymentOptionsProvider { ...@@ -148,7 +150,8 @@ class PaymentRequestSpec : public PaymentOptionsProvider {
mojom::PaymentShippingOption* selected_shipping_option_; mojom::PaymentShippingOption* selected_shipping_option_;
base::string16 selected_shipping_option_error_; base::string16 selected_shipping_option_error_;
std::unique_ptr<CurrencyFormatter> currency_formatter_; // One currency formatter is instantiated and cached per currency code.
std::map<std::string, CurrencyFormatter> currency_formatters_;
// A list/set of supported basic card networks. The list is used to keep the // A list/set of supported basic card networks. The list is used to keep the
// order in which they were specified by the merchant. The set is used for // order in which they were specified by the merchant. The set is used for
......
...@@ -341,4 +341,93 @@ TEST_F(PaymentRequestSpecTest, ShippingOptionsSelection_NoOptionsAtAll) { ...@@ -341,4 +341,93 @@ TEST_F(PaymentRequestSpecTest, ShippingOptionsSelection_NoOptionsAtAll) {
spec()->selected_shipping_option_error()); spec()->selected_shipping_option_error());
} }
TEST_F(PaymentRequestSpecTest, SingleCurrencyWithoutDisplayItems) {
mojom::PaymentDetailsPtr details = mojom::PaymentDetails::New();
mojom::PaymentItemPtr total = mojom::PaymentItem::New();
mojom::PaymentCurrencyAmountPtr amount = mojom::PaymentCurrencyAmount::New();
amount->currency = "USD";
total->amount = std::move(amount);
details->total = std::move(total);
RecreateSpecWithOptionsAndDetails(mojom::PaymentOptions::New(),
std::move(details));
// If the request only has a total, it must not have mixed currencies.
EXPECT_FALSE(spec()->IsMixedCurrency());
}
TEST_F(PaymentRequestSpecTest, SingleCurrencyWithDisplayItems) {
mojom::PaymentDetailsPtr details = mojom::PaymentDetails::New();
mojom::PaymentItemPtr total = mojom::PaymentItem::New();
mojom::PaymentCurrencyAmountPtr amount = mojom::PaymentCurrencyAmount::New();
amount->currency = "USD";
total->amount = std::move(amount);
details->total = std::move(total);
mojom::PaymentItemPtr display_item = mojom::PaymentItem::New();
mojom::PaymentCurrencyAmountPtr display_amount =
mojom::PaymentCurrencyAmount::New();
display_amount->currency = "USD";
display_item->amount = std::move(display_amount);
details->display_items.push_back(std::move(display_item));
RecreateSpecWithOptionsAndDetails(mojom::PaymentOptions::New(),
std::move(details));
// Both the total and the display item have matching currency codes, this
// isn't a mixed currency case.
EXPECT_FALSE(spec()->IsMixedCurrency());
}
TEST_F(PaymentRequestSpecTest, MultipleCurrenciesWithOneDisplayItem) {
mojom::PaymentDetailsPtr details = mojom::PaymentDetails::New();
mojom::PaymentItemPtr total = mojom::PaymentItem::New();
mojom::PaymentCurrencyAmountPtr amount = mojom::PaymentCurrencyAmount::New();
amount->currency = "USD";
total->amount = std::move(amount);
details->total = std::move(total);
mojom::PaymentItemPtr display_item = mojom::PaymentItem::New();
mojom::PaymentCurrencyAmountPtr display_amount =
mojom::PaymentCurrencyAmount::New();
display_amount->currency = "CAD";
display_item->amount = std::move(display_amount);
details->display_items.push_back(std::move(display_item));
RecreateSpecWithOptionsAndDetails(mojom::PaymentOptions::New(),
std::move(details));
// The display item currency and the total's currency don't match, this is a
// mixed currencies case.
EXPECT_TRUE(spec()->IsMixedCurrency());
}
TEST_F(PaymentRequestSpecTest, MultipleCurrenciesWithTwoDisplayItem) {
mojom::PaymentDetailsPtr details = mojom::PaymentDetails::New();
mojom::PaymentItemPtr total = mojom::PaymentItem::New();
mojom::PaymentCurrencyAmountPtr amount = mojom::PaymentCurrencyAmount::New();
amount->currency = "USD";
total->amount = std::move(amount);
details->total = std::move(total);
mojom::PaymentItemPtr display_item1 = mojom::PaymentItem::New();
mojom::PaymentCurrencyAmountPtr display_amount1 =
mojom::PaymentCurrencyAmount::New();
display_amount1->currency = "CAD";
display_item1->amount = std::move(display_amount1);
details->display_items.push_back(std::move(display_item1));
mojom::PaymentItemPtr display_item2 = mojom::PaymentItem::New();
mojom::PaymentCurrencyAmountPtr display_amount2 =
mojom::PaymentCurrencyAmount::New();
display_amount2->currency = "USD";
display_item2->amount = std::move(display_amount2);
details->display_items.push_back(std::move(display_item2));
RecreateSpecWithOptionsAndDetails(mojom::PaymentOptions::New(),
std::move(details));
// At least one of the display items has a different currency, this is a mixed
// currency case.
EXPECT_TRUE(spec()->IsMixedCurrency());
}
} // namespace payments } // namespace payments
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment