blob: cb86a0d93214a9302f95f02b9e6dff5ad3a2845d [file] [log] [blame]
// Copyright 2014 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/autofill/core/browser/ui/card_unmask_prompt_controller_impl.h"
#include <stddef.h>
#include "base/bind.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "components/autofill/core/browser/autofill_experiments.h"
#include "components/autofill/core/browser/autofill_metrics.h"
#include "components/autofill/core/browser/ui/card_unmask_prompt_view.h"
#include "components/autofill/core/common/autofill_pref_names.h"
#include "components/prefs/pref_service.h"
#include "grit/components_scaled_resources.h"
#include "grit/components_strings.h"
#include "ui/base/l10n/l10n_util.h"
namespace autofill {
CardUnmaskPromptControllerImpl::CardUnmaskPromptControllerImpl(
PrefService* pref_service,
bool is_off_the_record)
: pref_service_(pref_service),
new_card_link_clicked_(false),
is_off_the_record_(is_off_the_record),
card_unmask_view_(nullptr),
unmasking_result_(AutofillClient::NONE),
unmasking_initial_should_store_pan_(false),
unmasking_number_of_attempts_(0),
weak_pointer_factory_(this) {}
CardUnmaskPromptControllerImpl::~CardUnmaskPromptControllerImpl() {
if (card_unmask_view_)
card_unmask_view_->ControllerGone();
}
void CardUnmaskPromptControllerImpl::ShowPrompt(
CardUnmaskPromptView* card_unmask_view,
const CreditCard& card,
AutofillClient::UnmaskCardReason reason,
base::WeakPtr<CardUnmaskDelegate> delegate) {
if (card_unmask_view_)
card_unmask_view_->ControllerGone();
new_card_link_clicked_ = false;
shown_timestamp_ = base::Time::Now();
pending_response_ = CardUnmaskDelegate::UnmaskResponse();
card_unmask_view_ = card_unmask_view;
card_ = card;
reason_ = reason;
delegate_ = delegate;
card_unmask_view_->Show();
unmasking_result_ = AutofillClient::NONE;
unmasking_number_of_attempts_ = 0;
unmasking_initial_should_store_pan_ = GetStoreLocallyStartState();
AutofillMetrics::LogUnmaskPromptEvent(AutofillMetrics::UNMASK_PROMPT_SHOWN);
}
bool CardUnmaskPromptControllerImpl::AllowsRetry(
AutofillClient::PaymentsRpcResult result) {
if (result == AutofillClient::NETWORK_ERROR ||
result == AutofillClient::PERMANENT_FAILURE) {
return false;
}
return true;
}
void CardUnmaskPromptControllerImpl::OnVerificationResult(
AutofillClient::PaymentsRpcResult result) {
if (!card_unmask_view_)
return;
base::string16 error_message;
switch (result) {
case AutofillClient::SUCCESS:
break;
case AutofillClient::TRY_AGAIN_FAILURE: {
error_message = l10n_util::GetStringUTF16(
IDS_AUTOFILL_CARD_UNMASK_PROMPT_ERROR_TRY_AGAIN);
break;
}
case AutofillClient::PERMANENT_FAILURE: {
error_message = l10n_util::GetStringUTF16(
IDS_AUTOFILL_CARD_UNMASK_PROMPT_ERROR_PERMANENT);
break;
}
case AutofillClient::NETWORK_ERROR: {
error_message = l10n_util::GetStringUTF16(
IDS_AUTOFILL_CARD_UNMASK_PROMPT_ERROR_NETWORK);
break;
}
case AutofillClient::NONE:
NOTREACHED();
return;
}
unmasking_result_ = result;
AutofillMetrics::LogRealPanResult(result);
AutofillMetrics::LogUnmaskingDuration(base::Time::Now() - verify_timestamp_,
result);
card_unmask_view_->GotVerificationResult(error_message,
AllowsRetry(result));
}
void CardUnmaskPromptControllerImpl::OnUnmaskDialogClosed() {
card_unmask_view_ = nullptr;
LogOnCloseEvents();
if (delegate_.get())
delegate_->OnUnmaskPromptClosed();
}
void CardUnmaskPromptControllerImpl::LogOnCloseEvents() {
AutofillMetrics::UnmaskPromptEvent close_reason_event = GetCloseReasonEvent();
AutofillMetrics::LogUnmaskPromptEvent(close_reason_event);
AutofillMetrics::LogUnmaskPromptEventDuration(
base::Time::Now() - shown_timestamp_, close_reason_event);
if (close_reason_event == AutofillMetrics::UNMASK_PROMPT_CLOSED_NO_ATTEMPTS)
return;
if (close_reason_event ==
AutofillMetrics::UNMASK_PROMPT_CLOSED_ABANDON_UNMASKING) {
AutofillMetrics::LogTimeBeforeAbandonUnmasking(base::Time::Now() -
verify_timestamp_);
}
bool final_should_store_pan = pending_response_.should_store_pan;
if (unmasking_result_ == AutofillClient::SUCCESS && final_should_store_pan) {
AutofillMetrics::LogUnmaskPromptEvent(
AutofillMetrics::UNMASK_PROMPT_SAVED_CARD_LOCALLY);
}
if (CanStoreLocally()) {
// Tracking changes in local save preference.
AutofillMetrics::UnmaskPromptEvent event;
if (unmasking_initial_should_store_pan_ && final_should_store_pan) {
event = AutofillMetrics::UNMASK_PROMPT_LOCAL_SAVE_DID_NOT_OPT_OUT;
} else if (!unmasking_initial_should_store_pan_ &&
!final_should_store_pan) {
event = AutofillMetrics::UNMASK_PROMPT_LOCAL_SAVE_DID_NOT_OPT_IN;
} else if (unmasking_initial_should_store_pan_ && !final_should_store_pan) {
event = AutofillMetrics::UNMASK_PROMPT_LOCAL_SAVE_DID_OPT_OUT;
} else {
event = AutofillMetrics::UNMASK_PROMPT_LOCAL_SAVE_DID_OPT_IN;
}
AutofillMetrics::LogUnmaskPromptEvent(event);
}
}
AutofillMetrics::UnmaskPromptEvent
CardUnmaskPromptControllerImpl::GetCloseReasonEvent() {
if (unmasking_number_of_attempts_ == 0)
return AutofillMetrics::UNMASK_PROMPT_CLOSED_NO_ATTEMPTS;
// If NONE and we have a pending request, we have a pending GetRealPan
// request.
if (unmasking_result_ == AutofillClient::NONE)
return AutofillMetrics::UNMASK_PROMPT_CLOSED_ABANDON_UNMASKING;
if (unmasking_result_ == AutofillClient::SUCCESS) {
return unmasking_number_of_attempts_ == 1
? AutofillMetrics::UNMASK_PROMPT_UNMASKED_CARD_FIRST_ATTEMPT
: AutofillMetrics::UNMASK_PROMPT_UNMASKED_CARD_AFTER_FAILED_ATTEMPTS;
} else {
return AllowsRetry(unmasking_result_)
? AutofillMetrics::
UNMASK_PROMPT_CLOSED_FAILED_TO_UNMASK_RETRIABLE_FAILURE
: AutofillMetrics::
UNMASK_PROMPT_CLOSED_FAILED_TO_UNMASK_NON_RETRIABLE_FAILURE;
}
}
void CardUnmaskPromptControllerImpl::OnUnmaskResponse(
const base::string16& cvc,
const base::string16& exp_month,
const base::string16& exp_year,
bool should_store_pan) {
verify_timestamp_ = base::Time::Now();
unmasking_number_of_attempts_++;
unmasking_result_ = AutofillClient::NONE;
card_unmask_view_->DisableAndWaitForVerification();
DCHECK(InputCvcIsValid(cvc));
base::TrimWhitespace(cvc, base::TRIM_ALL, &pending_response_.cvc);
if (ShouldRequestExpirationDate()) {
DCHECK(InputExpirationIsValid(exp_month, exp_year));
pending_response_.exp_month = exp_month;
pending_response_.exp_year = exp_year;
}
if (CanStoreLocally()) {
pending_response_.should_store_pan = should_store_pan;
// Remember the last choice the user made (on this device).
pref_service_->SetBoolean(
prefs::kAutofillWalletImportStorageCheckboxState, should_store_pan);
} else {
DCHECK(!should_store_pan);
pending_response_.should_store_pan = false;
}
delegate_->OnUnmaskResponse(pending_response_);
}
void CardUnmaskPromptControllerImpl::NewCardLinkClicked() {
new_card_link_clicked_ = true;
}
base::string16 CardUnmaskPromptControllerImpl::GetWindowTitle() const {
#if defined(OS_IOS)
// The iOS UI has less room for the title and places a "Verify" button right
// next to it so the full title ("Verify your" + type and last four) is
// unnecessary.
return card_.TypeAndLastFourDigits();
#else
int ids;
if (reason_ == AutofillClient::UNMASK_FOR_PAYMENT_REQUEST)
ids = IDS_AUTOFILL_CARD_UNMASK_PROMPT_PAY_TITLE;
else if (ShouldRequestExpirationDate())
ids = IDS_AUTOFILL_CARD_UNMASK_PROMPT_UPDATE_TITLE;
else
ids = IDS_AUTOFILL_CARD_UNMASK_PROMPT_TITLE;
return l10n_util::GetStringFUTF16(ids, card_.TypeAndLastFourDigits());
#endif
}
base::string16 CardUnmaskPromptControllerImpl::GetInstructionsMessage() const {
if (reason_ == AutofillClient::UNMASK_FOR_PAYMENT_REQUEST) {
return l10n_util::GetStringUTF16(
card_.type() == kAmericanExpressCard
? IDS_AUTOFILL_CARD_UNMASK_PROMPT_PAY_INSTRUCTIONS_AMEX
: IDS_AUTOFILL_CARD_UNMASK_PROMPT_PAY_INSTRUCTIONS);
}
if (ShouldRequestExpirationDate()) {
return l10n_util::GetStringUTF16(
card_.type() == kAmericanExpressCard
? IDS_AUTOFILL_CARD_UNMASK_PROMPT_INSTRUCTIONS_EXPIRED_AMEX
: IDS_AUTOFILL_CARD_UNMASK_PROMPT_INSTRUCTIONS_EXPIRED);
}
return l10n_util::GetStringUTF16(
card_.type() == kAmericanExpressCard
? IDS_AUTOFILL_CARD_UNMASK_PROMPT_INSTRUCTIONS_AMEX
: IDS_AUTOFILL_CARD_UNMASK_PROMPT_INSTRUCTIONS);
}
base::string16 CardUnmaskPromptControllerImpl::GetOkButtonLabel() const {
return l10n_util::GetStringUTF16(
reason_ == AutofillClient::UNMASK_FOR_PAYMENT_REQUEST
? IDS_AUTOFILL_CARD_UNMASK_CONTINUE_BUTTON
: IDS_AUTOFILL_CARD_UNMASK_CONFIRM_BUTTON);
}
int CardUnmaskPromptControllerImpl::GetCvcImageRid() const {
return card_.type() == kAmericanExpressCard ? IDR_CREDIT_CARD_CVC_HINT_AMEX
: IDR_CREDIT_CARD_CVC_HINT;
}
bool CardUnmaskPromptControllerImpl::ShouldRequestExpirationDate() const {
return card_.GetServerStatus() == CreditCard::EXPIRED ||
new_card_link_clicked_;
}
bool CardUnmaskPromptControllerImpl::CanStoreLocally() const {
// Never offer to save for incognito.
if (is_off_the_record_)
return false;
if (reason_ == AutofillClient::UNMASK_FOR_PAYMENT_REQUEST)
return false;
return OfferStoreUnmaskedCards();
}
bool CardUnmaskPromptControllerImpl::GetStoreLocallyStartState() const {
return pref_service_->GetBoolean(
prefs::kAutofillWalletImportStorageCheckboxState);
}
bool CardUnmaskPromptControllerImpl::InputCvcIsValid(
const base::string16& input_text) const {
base::string16 trimmed_text;
base::TrimWhitespace(input_text, base::TRIM_ALL, &trimmed_text);
size_t input_size = card_.type() == kAmericanExpressCard ? 4 : 3;
return trimmed_text.size() == input_size &&
base::ContainsOnlyChars(trimmed_text,
base::ASCIIToUTF16("0123456789"));
}
bool CardUnmaskPromptControllerImpl::InputExpirationIsValid(
const base::string16& month,
const base::string16& year) const {
if ((month.size() != 2U && month.size() != 1U) ||
(year.size() != 4U && year.size() != 2U)) {
return false;
}
int month_value = 0, year_value = 0;
if (!base::StringToInt(month, &month_value) ||
!base::StringToInt(year, &year_value)) {
return false;
}
if (month_value < 1 || month_value > 12)
return false;
base::Time::Exploded now;
base::Time::Now().LocalExplode(&now);
// Convert 2 digit year to 4 digit year.
if (year_value < 100)
year_value += (now.year / 100) * 100;
if (year_value < now.year)
return false;
if (year_value > now.year)
return true;
return month_value >= now.month;
}
base::TimeDelta CardUnmaskPromptControllerImpl::GetSuccessMessageDuration()
const {
return base::TimeDelta::FromMilliseconds(500);
}
} // namespace autofill