blob: be99d4e87092f85277f21abc652baadf12a18ac7 [file] [log] [blame]
// 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 "chrome/browser/ui/views/payments/credit_card_editor_view_controller.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/logging.h"
#include "base/strings/string16.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "chrome/browser/ui/autofill/payments/autofill_dialog_models.h"
#include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
#include "chrome/browser/ui/singleton_tabs.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/payments/payment_request_dialog_view.h"
#include "chrome/browser/ui/views/payments/payment_request_dialog_view_ids.h"
#include "chrome/browser/ui/views/payments/payment_request_views_util.h"
#include "chrome/browser/ui/views/payments/validating_combobox.h"
#include "chrome/browser/ui/views/payments/validating_textfield.h"
#include "chrome/grit/generated_resources.h"
#include "components/autofill/core/browser/address_combobox_model.h"
#include "components/autofill/core/browser/autofill_data_util.h"
#include "components/autofill/core/browser/autofill_type.h"
#include "components/autofill/core/browser/credit_card.h"
#include "components/autofill/core/browser/field_types.h"
#include "components/autofill/core/browser/payments/payments_service_url.h"
#include "components/autofill/core/browser/personal_data_manager.h"
#include "components/autofill/core/browser/validation.h"
#include "components/autofill/core/common/autofill_clock.h"
#include "components/autofill/core/common/autofill_constants.h"
#include "components/payments/content/payment_request_spec.h"
#include "components/payments/content/payment_request_state.h"
#include "components/payments/core/payment_request_data_util.h"
#include "components/payments/core/strings_util.h"
#include "components/strings/grit/components_strings.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/native_theme/native_theme.h"
#include "ui/views/controls/button/md_text_button.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/styled_label.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/layout/grid_layout.h"
#include "ui/views/view.h"
namespace payments {
namespace {
// Opacity of card network icons when they are not selected and are displayed
// alongside a selected icon.
const float kDimmedCardIconOpacity = 0.33f;
// This is not quite right but is the closest server type that wasn't already
// used.
const auto kBillingAddressType = autofill::ADDRESS_BILLING_LINE1;
bool IsCardExpired(const base::string16& month,
const base::string16& year,
const std::string& app_locale) {
autofill::CreditCard card;
card.SetExpirationMonthFromString(month, app_locale);
card.SetExpirationYearFromString(year);
return card.IsExpired(autofill::AutofillClock::Now());
}
// Validates the two comboboxes used for expiration date.
class ExpirationDateValidationDelegate : public ValidationDelegate {
public:
ExpirationDateValidationDelegate(EditorViewController* controller,
const std::string& app_locale,
bool initially_valid)
: controller_(controller),
app_locale_(app_locale),
initially_valid_(initially_valid) {}
bool IsValidTextfield(views::Textfield* textfield,
base::string16* error_message) override {
NOTREACHED();
return true;
}
bool IsValidCombobox(views::Combobox* combobox,
base::string16* error_message) override {
// View will have no parent if it's not been attached yet. Use initial
// validity state.
views::View* view_parent = combobox->parent();
if (!view_parent) {
*error_message =
initially_valid_
? base::string16()
: l10n_util::GetStringUTF16(
IDS_PAYMENTS_VALIDATION_INVALID_CREDIT_CARD_EXPIRED);
return initially_valid_;
}
// Get the combined date from the month and year dropdowns.
views::Combobox* month_combobox = static_cast<views::Combobox*>(
view_parent->GetViewByID(EditorViewController::GetInputFieldViewId(
autofill::CREDIT_CARD_EXP_MONTH)));
base::string16 month =
month_combobox->model()->GetItemAt(month_combobox->selected_index());
views::Combobox* year_combobox = static_cast<views::Combobox*>(
view_parent->GetViewByID(EditorViewController::GetInputFieldViewId(
autofill::CREDIT_CARD_EXP_4_DIGIT_YEAR)));
base::string16 year =
year_combobox->model()->GetItemAt(year_combobox->selected_index());
bool is_expired = IsCardExpired(month, year, app_locale_);
month_combobox->SetInvalid(is_expired);
year_combobox->SetInvalid(is_expired);
*error_message =
is_expired ? l10n_util::GetStringUTF16(
IDS_PAYMENTS_VALIDATION_INVALID_CREDIT_CARD_EXPIRED)
: base::string16();
return !is_expired;
}
bool TextfieldValueChanged(views::Textfield* textfield,
bool was_blurred) override {
NOTREACHED();
return true;
}
bool ComboboxValueChanged(views::Combobox* combobox) override {
base::string16 error_message;
bool is_valid = IsValidCombobox(combobox, &error_message);
controller_->DisplayErrorMessageForField(
autofill::CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR, error_message);
return is_valid;
}
void ComboboxModelChanged(views::Combobox* combobox) override {}
private:
EditorViewController* controller_;
const std::string app_locale_;
bool initially_valid_;
DISALLOW_COPY_AND_ASSIGN(ExpirationDateValidationDelegate);
};
} // namespace
CreditCardEditorViewController::CreditCardEditorViewController(
PaymentRequestSpec* spec,
PaymentRequestState* state,
PaymentRequestDialogView* dialog,
BackNavigationType back_navigation,
int next_ui_tag,
base::OnceClosure on_edited,
base::OnceCallback<void(const autofill::CreditCard&)> on_added,
autofill::CreditCard* credit_card,
bool is_incognito)
: EditorViewController(spec, state, dialog, back_navigation, is_incognito),
on_edited_(std::move(on_edited)),
on_added_(std::move(on_added)),
credit_card_to_edit_(credit_card),
add_billing_address_button_tag_(next_ui_tag) {
if (spec)
supported_card_networks_ = spec->supported_card_networks_set();
}
CreditCardEditorViewController::~CreditCardEditorViewController() {}
// Creates the "Cards accepted" view with a row of icons at the top of the
// credit card editor.
// +----------------------------------------------+
// | Cards Accepted |
// | |
// | | VISA | | MC | | AMEX | |
// +----------------------------------------------+
std::unique_ptr<views::View>
CreditCardEditorViewController::CreateHeaderView() {
std::unique_ptr<views::View> view = std::make_unique<views::View>();
// 9dp is required between the first row (label) and second row (icons).
constexpr int kRowVerticalSpacing = 9;
// 6dp is added to the bottom padding, for a total of 12 between the icons and
// the first input field.
constexpr int kRowBottomPadding = 6;
auto layout = std::make_unique<views::BoxLayout>(
views::BoxLayout::kVertical,
gfx::Insets(kRowBottomPadding, kPaymentRequestRowHorizontalInsets),
kRowVerticalSpacing);
layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_START);
layout->set_cross_axis_alignment(
views::BoxLayout::CROSS_AXIS_ALIGNMENT_START);
view->SetLayoutManager(std::move(layout));
// "Cards accepted" label is "hint" grey.
view->AddChildView(CreateHintLabel(GetAcceptedCardTypesText(
spec()->supported_card_types_set()))
.release());
// 8dp padding is required between icons.
constexpr int kPaddingBetweenCardIcons = 8;
std::unique_ptr<views::View> icons_row = std::make_unique<views::View>();
icons_row->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::kHorizontal, gfx::Insets(), kPaddingBetweenCardIcons));
std::string selected_network =
credit_card_to_edit_ ? autofill::data_util::GetPaymentRequestData(
credit_card_to_edit_->network())
.basic_card_issuer_network
: "";
constexpr gfx::Size kCardIconSize = gfx::Size(30, 18);
for (const std::string& supported_network :
spec()->supported_card_networks()) {
const std::string autofill_card_type =
autofill::data_util::GetIssuerNetworkForBasicCardIssuerNetwork(
supported_network);
// Icon is fully opaque if no network is selected, or if it is the selected
// network.
float opacity =
selected_network.empty() || selected_network == supported_network
? 1.0f
: kDimmedCardIconOpacity;
std::unique_ptr<views::ImageView> card_icon_view = CreateInstrumentIconView(
autofill::data_util::GetPaymentRequestData(autofill_card_type)
.icon_resource_id,
gfx::ImageSkia(), base::UTF8ToUTF16(supported_network), opacity);
card_icon_view->SetImageSize(kCardIconSize);
// Keep track of this card icon to later adjust opacity.
card_icons_[supported_network] = card_icon_view.get();
icons_row->AddChildView(card_icon_view.release());
}
view->AddChildView(icons_row.release());
// If dealing with a server card, we add "From Google Payments" with an edit
// link.
if (IsEditingServerCard()) {
std::unique_ptr<views::View> data_source = std::make_unique<views::View>();
data_source->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::kHorizontal, gfx::Insets(),
kPaddingBetweenCardIcons));
// "From Google Payments".
data_source->AddChildView(
CreateHintLabel(
l10n_util::GetStringUTF16(IDS_AUTOFILL_FROM_GOOGLE_ACCOUNT_LONG))
.release());
// "Edit" link.
base::string16 link_text =
l10n_util::GetStringUTF16(IDS_AUTOFILL_WALLET_MANAGEMENT_LINK_TEXT);
auto edit_link = std::make_unique<views::StyledLabel>(link_text, this);
edit_link->set_id(
static_cast<int>(DialogViewID::GOOGLE_PAYMENTS_EDIT_LINK_LABEL));
edit_link->AddStyleRange(
gfx::Range(0, link_text.size()),
views::StyledLabel::RangeStyleInfo::CreateForLink());
edit_link->SizeToFit(0);
data_source->AddChildView(edit_link.release());
view->AddChildView(data_source.release());
}
return view;
}
std::unique_ptr<views::View>
CreditCardEditorViewController::CreateCustomFieldView(
autofill::ServerFieldType type,
views::View** focusable_field,
bool* valid,
base::string16* error_message) {
DCHECK_EQ(type, autofill::CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR);
std::unique_ptr<views::View> view = std::make_unique<views::View>();
if (IsEditingServerCard()) {
std::unique_ptr<views::Label> exp_label = std::make_unique<views::Label>(
credit_card_to_edit_->ExpirationDateForDisplay());
exp_label->set_id(
GetInputFieldViewId(autofill::CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR));
exp_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
view = std::move(exp_label);
} else {
// Two comboboxes, one for month and the other for year.
views::GridLayout* combobox_layout =
view->SetLayoutManager(std::make_unique<views::GridLayout>(view.get()));
views::ColumnSet* columns = combobox_layout->AddColumnSet(0);
columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::CENTER,
1.0, views::GridLayout::USE_PREF, 0, 0);
// Space between the two comboboxes.
constexpr int kHorizontalSpacing = 8;
columns->AddPaddingColumn(views::GridLayout::kFixedSize,
kHorizontalSpacing);
columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::CENTER,
1.0, views::GridLayout::USE_PREF, 0, 0);
combobox_layout->StartRow(views::GridLayout::kFixedSize, 0);
constexpr int kInputFieldHeight = 28;
EditorField tmp_month{
autofill::CREDIT_CARD_EXP_MONTH,
l10n_util::GetStringUTF16(IDS_SETTINGS_CREDIT_CARD_EXPIRATION_MONTH),
EditorField::LengthHint::HINT_SHORT,
/*required=*/true, EditorField::ControlType::COMBOBOX};
std::unique_ptr<ValidatingCombobox> month_combobox =
CreateComboboxForField(tmp_month, error_message);
*focusable_field = month_combobox.get();
combobox_layout->AddView(month_combobox.release(), 1, 1,
views::GridLayout::FILL, views::GridLayout::FILL,
0, kInputFieldHeight);
EditorField tmp_year{
autofill::CREDIT_CARD_EXP_4_DIGIT_YEAR,
l10n_util::GetStringUTF16(IDS_SETTINGS_CREDIT_CARD_EXPIRATION_YEAR),
EditorField::LengthHint::HINT_SHORT,
/*required=*/true, EditorField::ControlType::COMBOBOX};
std::unique_ptr<ValidatingCombobox> year_combobox =
CreateComboboxForField(tmp_year, error_message);
combobox_layout->AddView(year_combobox.release(), 1, 1,
views::GridLayout::FILL, views::GridLayout::FILL,
0, kInputFieldHeight);
}
// Set the initial validity of the custom view.
base::string16 month =
GetInitialValueForType(autofill::CREDIT_CARD_EXP_MONTH);
base::string16 year =
GetInitialValueForType(autofill::CREDIT_CARD_EXP_4_DIGIT_YEAR);
*valid = !IsCardExpired(month, year, state()->GetApplicationLocale());
return view;
}
std::unique_ptr<views::View>
CreditCardEditorViewController::CreateExtraViewForField(
autofill::ServerFieldType type) {
if (type != kBillingAddressType)
return nullptr;
std::unique_ptr<views::View> button_view = std::make_unique<views::View>();
button_view->SetLayoutManager(std::make_unique<views::FillLayout>());
// The button to add new billing addresses.
std::unique_ptr<views::Button> add_button(
views::MdTextButton::Create(this, l10n_util::GetStringUTF16(IDS_ADD)));
add_button->set_id(
static_cast<int>(DialogViewID::ADD_BILLING_ADDRESS_BUTTON));
add_button->set_tag(add_billing_address_button_tag_);
add_button->SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
button_view->AddChildView(add_button.release());
return button_view;
}
bool CreditCardEditorViewController::IsEditingExistingItem() {
return !!credit_card_to_edit_;
}
std::vector<EditorField> CreditCardEditorViewController::GetFieldDefinitions() {
bool is_server_card = IsEditingServerCard();
return std::vector<EditorField>{
{autofill::CREDIT_CARD_NUMBER,
l10n_util::GetStringUTF16(IDS_SETTINGS_CREDIT_CARD_NUMBER),
EditorField::LengthHint::HINT_SHORT, /*required=*/true,
is_server_card ? EditorField::ControlType::READONLY_LABEL
: EditorField::ControlType::TEXTFIELD_NUMBER},
{autofill::CREDIT_CARD_NAME_FULL,
l10n_util::GetStringUTF16(IDS_SETTINGS_NAME_ON_CREDIT_CARD),
EditorField::LengthHint::HINT_SHORT, /*required=*/true,
is_server_card ? EditorField::ControlType::READONLY_LABEL
: EditorField::ControlType::TEXTFIELD},
{autofill::CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR,
l10n_util::GetStringUTF16(IDS_SETTINGS_CREDIT_CARD_EXPIRATION_DATE),
EditorField::LengthHint::HINT_SHORT, /*required=*/true,
EditorField::ControlType::CUSTOMFIELD},
{kBillingAddressType,
l10n_util::GetStringUTF16(IDS_AUTOFILL_FIELD_LABEL_BILLING_ADDRESS),
EditorField::LengthHint::HINT_SHORT, /*required=*/true,
EditorField::ControlType::COMBOBOX}};
}
base::string16 CreditCardEditorViewController::GetInitialValueForType(
autofill::ServerFieldType type) {
if (!credit_card_to_edit_ || type == kBillingAddressType)
return base::string16();
base::string16 info = credit_card_to_edit_->GetInfo(
autofill::AutofillType(type), state()->GetApplicationLocale());
return type == autofill::CREDIT_CARD_NUMBER
? data_util::FormatCardNumberForDisplay(info)
: info;
}
bool CreditCardEditorViewController::ValidateModelAndSave() {
if (IsEditingServerCard()) {
views::Combobox* address_combobox = static_cast<views::Combobox*>(
dialog()->GetViewByID(GetInputFieldViewId(kBillingAddressType)));
if (address_combobox->invalid())
return false;
autofill::AddressComboboxModel* model =
static_cast<autofill::AddressComboboxModel*>(address_combobox->model());
credit_card_to_edit_->set_billing_address_id(
model->GetItemIdentifierAt(address_combobox->selected_index()));
if (!is_incognito()) {
state()->GetPersonalDataManager()->UpdateServerCardMetadata(
*credit_card_to_edit_);
}
return true;
}
const std::string& locale = state()->GetApplicationLocale();
// Use a temporary object for validation.
autofill::CreditCard credit_card;
credit_card.set_origin(autofill::kSettingsOrigin);
if (!ValidateInputFields())
return false;
for (const auto& field : text_fields()) {
// ValidatingTextfield* is the key, EditorField is the value.
DCHECK_EQ(autofill::CREDIT_CARD,
autofill::AutofillType(field.second.type).group());
credit_card.SetInfo(autofill::AutofillType(field.second.type),
field.first->text(), locale);
}
for (const auto& field : comboboxes()) {
// ValidatingCombobox* is the key, EditorField is the value.
ValidatingCombobox* combobox = field.first;
if (field.second.type == kBillingAddressType) {
autofill::AddressComboboxModel* model =
static_cast<autofill::AddressComboboxModel*>(combobox->model());
credit_card.set_billing_address_id(
model->GetItemIdentifierAt(combobox->selected_index()));
} else {
credit_card.SetInfo(autofill::AutofillType(field.second.type),
combobox->GetTextForRow(combobox->selected_index()),
locale);
}
}
// TODO(crbug.com/711365): Display global error message.
if (autofill::GetCompletionStatusForCard(
credit_card, locale,
state()->GetPersonalDataManager()->GetProfiles()) !=
autofill::CREDIT_CARD_COMPLETE) {
return false;
}
if (!credit_card_to_edit_) {
if (!is_incognito()) {
// Add the card (will not add a duplicate).
state()->GetPersonalDataManager()->AddCreditCard(credit_card);
}
std::move(on_added_).Run(credit_card);
} else {
credit_card_to_edit_->set_billing_address_id(
credit_card.billing_address_id());
// We were in edit mode. Copy the data from the temporary object to retain
// the edited object's other properties (use count, use date, guid, etc.).
for (const auto& field : text_fields()) {
credit_card_to_edit_->SetInfo(
autofill::AutofillType(field.second.type),
credit_card.GetInfo(autofill::AutofillType(field.second.type),
locale),
locale);
}
for (const auto& field : comboboxes()) {
// The billing address is transfered above.
if (field.second.type == kBillingAddressType)
continue;
credit_card_to_edit_->SetInfo(
autofill::AutofillType(field.second.type),
credit_card.GetInfo(autofill::AutofillType(field.second.type),
locale),
locale);
}
if (!is_incognito())
state()->GetPersonalDataManager()->UpdateCreditCard(
*credit_card_to_edit_);
std::move(on_edited_).Run();
}
return true;
}
std::unique_ptr<ValidationDelegate>
CreditCardEditorViewController::CreateValidationDelegate(
const EditorField& field) {
if (field.type == autofill::CREDIT_CARD_EXP_MONTH ||
field.type == autofill::CREDIT_CARD_EXP_4_DIGIT_YEAR) {
bool initially_valid =
credit_card_to_edit_
? !credit_card_to_edit_->IsExpired(autofill::AutofillClock::Now())
: true;
return std::make_unique<ExpirationDateValidationDelegate>(
this, state()->GetApplicationLocale(), initially_valid);
}
// The supported card networks for non-cc-number types are not passed to avoid
// the data copy in the delegate.
return std::make_unique<
CreditCardEditorViewController::CreditCardValidationDelegate>(field,
this);
}
std::unique_ptr<ui::ComboboxModel>
CreditCardEditorViewController::GetComboboxModelForType(
const autofill::ServerFieldType& type) {
switch (type) {
case autofill::CREDIT_CARD_EXP_MONTH: {
return std::make_unique<autofill::MonthComboboxModel>();
}
case autofill::CREDIT_CARD_EXP_4_DIGIT_YEAR:
return std::make_unique<autofill::YearComboboxModel>(
credit_card_to_edit_ ? credit_card_to_edit_->expiration_year() : 0);
case kBillingAddressType:
// The combobox filled with potential billing addresses. It's fine to pass
// empty string as the default selected guid if there are no cards being
// edited.
return std::make_unique<autofill::AddressComboboxModel>(
*state()->GetPersonalDataManager(), state()->GetApplicationLocale(),
credit_card_to_edit_ ? credit_card_to_edit_->billing_address_id()
: "");
default:
NOTREACHED();
break;
}
return std::unique_ptr<ui::ComboboxModel>();
}
void CreditCardEditorViewController::StyledLabelLinkClicked(
views::StyledLabel* label,
const gfx::Range& range,
int event_flags) {
// The only thing that can trigger this is the user clicking on the "edit"
// link for a server card.
chrome::ScopedTabbedBrowserDisplayer displayer(dialog()->GetProfile());
ShowSingletonTab(displayer.browser(),
autofill::payments::GetManageAddressesUrl());
}
void CreditCardEditorViewController::SelectBasicCardNetworkIcon(
const std::string& basic_card_network) {
// If empty string was passed or if the icon representing |basic_card_network|
// is not present (i.e. not supported), all icons have full opacity.
bool full_opacity =
card_icons_.find(basic_card_network) == card_icons_.end() ||
basic_card_network.empty();
for (auto network_icon_it : card_icons_) {
float target_opacity =
full_opacity || network_icon_it.first == basic_card_network
? 1.0f
: kDimmedCardIconOpacity;
network_icon_it.second->layer()->SetOpacity(target_opacity);
network_icon_it.second->layer()->ScheduleDraw();
}
}
void CreditCardEditorViewController::FillContentView(
views::View* content_view) {
EditorViewController::FillContentView(content_view);
// We need to search from the content view here, since the dialog may not have
// the content view added to it yet.
views::Combobox* combobox = static_cast<views::Combobox*>(
content_view->GetViewByID(GetInputFieldViewId(kBillingAddressType)));
// When the combobox has a single item, it's because it has no addresses
// (otherwise, it would have the select header, and a separator before the
// first address to choose from).
DCHECK(combobox);
combobox->SetEnabled(combobox->GetRowCount() > 1);
}
bool CreditCardEditorViewController::IsValidCreditCardNumber(
const base::string16& card_number,
base::string16* error_message) {
return autofill::IsValidCreditCardNumberForBasicCardNetworks(
card_number, supported_card_networks_, error_message);
// TODO(crbug.com/725604): The UI should offer to load / update the existing
// credit card info if another local credit card has already been created with
// this number. (Does not apply to server cards, which can be accessed only in
// tokenized form through Google Pay.)
}
base::string16 CreditCardEditorViewController::GetSheetTitle() {
if (!credit_card_to_edit_)
return l10n_util::GetStringUTF16(IDS_PAYMENTS_ADD_CARD);
// Gets the completion message, or empty if nothing is missing from the card.
base::string16 title = autofill::GetCompletionMessageForCard(
autofill::GetCompletionStatusForCard(
*credit_card_to_edit_, state()->GetApplicationLocale(),
state()->GetPersonalDataManager()->GetProfiles()));
return title.empty() ? l10n_util::GetStringUTF16(IDS_PAYMENTS_EDIT_CARD)
: title;
}
void CreditCardEditorViewController::ButtonPressed(views::Button* sender,
const ui::Event& event) {
if (sender->tag() == add_billing_address_button_tag_) {
dialog()->ShowShippingAddressEditor(
BackNavigationType::kOneStep,
/*on_edited=*/
base::OnceClosure(),
/*on_added=*/
base::BindOnce(
&CreditCardEditorViewController::AddAndSelectNewBillingAddress,
base::Unretained(this)),
/*profile=*/nullptr);
} else {
EditorViewController::ButtonPressed(sender, event);
}
}
void CreditCardEditorViewController::AddAndSelectNewBillingAddress(
const autofill::AutofillProfile& profile) {
state()->AddAutofillShippingProfile(false, profile);
views::Combobox* address_combobox = static_cast<views::Combobox*>(
dialog()->GetViewByID(GetInputFieldViewId(kBillingAddressType)));
autofill::AddressComboboxModel* model =
static_cast<autofill::AddressComboboxModel*>(address_combobox->model());
int index = model->AddNewProfile(profile);
// SetSelectedIndex doesn't trigger a perform action notification, which is
// needed to update the valid state.
address_combobox->SetSelectedRow(index);
// The combobox might be initially disabled in FillContentView, but we've
// added an item; check if we should re-enable it.
address_combobox->SetEnabled(address_combobox->GetRowCount() > 1);
// But it needs to be blured at least once.
address_combobox->OnBlur();
}
bool CreditCardEditorViewController::IsEditingServerCard() const {
return credit_card_to_edit_ && credit_card_to_edit_->record_type() !=
autofill::CreditCard::LOCAL_CARD;
}
CreditCardEditorViewController::CreditCardValidationDelegate::
CreditCardValidationDelegate(const EditorField& field,
CreditCardEditorViewController* controller)
: field_(field), controller_(controller) {}
CreditCardEditorViewController::CreditCardValidationDelegate::
~CreditCardValidationDelegate() {}
bool CreditCardEditorViewController::CreditCardValidationDelegate::
ShouldFormat() {
return field_.type == autofill::CREDIT_CARD_NUMBER;
}
base::string16
CreditCardEditorViewController::CreditCardValidationDelegate::Format(
const base::string16& text) {
return data_util::FormatCardNumberForDisplay(text);
}
bool CreditCardEditorViewController::CreditCardValidationDelegate::
IsValidTextfield(views::Textfield* textfield,
base::string16* error_message) {
return ValidateValue(textfield->text(), error_message);
}
bool CreditCardEditorViewController::CreditCardValidationDelegate::
IsValidCombobox(views::Combobox* combobox, base::string16* error_message) {
return ValidateCombobox(combobox, error_message);
}
bool CreditCardEditorViewController::CreditCardValidationDelegate::
TextfieldValueChanged(views::Textfield* textfield, bool was_blurred) {
// The only behavior pre-blur is selecting the card icon.
if (field_.type == autofill::CREDIT_CARD_NUMBER) {
std::string basic_card_network =
autofill::data_util::GetPaymentRequestData(
autofill::CreditCard::GetCardNetwork(textfield->text()))
.basic_card_issuer_network;
controller_->SelectBasicCardNetworkIcon(basic_card_network);
}
// We return true if the field was not yet blurred, because validation should
// not occur yet.
if (!was_blurred)
return true;
base::string16 error_message;
bool is_valid = ValidateValue(textfield->text(), &error_message);
controller_->DisplayErrorMessageForField(field_.type, error_message);
return is_valid;
}
bool CreditCardEditorViewController::CreditCardValidationDelegate::
ComboboxValueChanged(views::Combobox* combobox) {
base::string16 error_message;
bool is_valid = ValidateCombobox(combobox, nullptr);
controller_->DisplayErrorMessageForField(field_.type, error_message);
return is_valid;
}
bool CreditCardEditorViewController::CreditCardValidationDelegate::
ValidateValue(const base::string16& value, base::string16* error_message) {
if (!value.empty()) {
base::string16 local_error_message;
bool is_valid = false;
if (field_.type == autofill::CREDIT_CARD_NUMBER) {
is_valid =
controller_->IsValidCreditCardNumber(value, &local_error_message);
} else {
is_valid =
autofill::IsValidForType(value, field_.type, &local_error_message);
}
if (error_message)
*error_message = local_error_message;
return is_valid;
}
if (error_message && field_.required) {
*error_message = l10n_util::GetStringUTF16(
IDS_PREF_EDIT_DIALOG_FIELD_REQUIRED_VALIDATION_MESSAGE);
}
return !field_.required;
}
bool CreditCardEditorViewController::CreditCardValidationDelegate::
ValidateCombobox(views::Combobox* combobox, base::string16* error_message) {
// The billing address ID is the selected item identifier and not the combobox
// value itself.
if (field_.type == kBillingAddressType) {
// TODO(crbug.com/718905) Find a way to deal with existing incomplete
// addresses when choosing them as billing addresses.
autofill::AddressComboboxModel* model =
static_cast<autofill::AddressComboboxModel*>(combobox->model());
if (model->GetItemIdentifierAt(combobox->selected_index()).empty()) {
if (error_message) {
*error_message =
l10n_util::GetStringUTF16(IDS_PAYMENTS_BILLING_ADDRESS_REQUIRED);
}
return false;
}
return true;
}
return ValidateValue(combobox->GetTextForRow(combobox->selected_index()),
error_message);
}
bool CreditCardEditorViewController::GetSheetId(DialogViewID* sheet_id) {
*sheet_id = DialogViewID::CREDIT_CARD_EDITOR_SHEET;
return true;
}
} // namespace payments