1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/autofill/card_unmask_prompt_controller_impl.h"
8 #include "base/prefs/pref_service.h"
9 #include "base/strings/string_number_conversions.h"
10 #include "base/strings/string_util.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/browser/autofill/risk_util.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/browser/ui/autofill/card_unmask_prompt_view.h"
15 #include "chrome/grit/generated_resources.h"
16 #include "components/autofill/core/browser/autofill_experiments.h"
17 #include "components/autofill/core/browser/autofill_metrics.h"
18 #include "components/autofill/core/common/autofill_pref_names.h"
19 #include "components/user_prefs/user_prefs.h"
20 #include "content/public/browser/web_contents.h"
21 #include "grit/theme_resources.h"
22 #include "ui/base/l10n/l10n_util.h"
26 CardUnmaskPromptControllerImpl::CardUnmaskPromptControllerImpl(
27 content::WebContents
* web_contents
)
28 : web_contents_(web_contents
),
29 card_unmask_view_(nullptr),
30 unmasking_result_(AutofillClient::NONE
),
31 unmasking_initial_should_store_pan_(false),
32 unmasking_number_of_attempts_(0),
33 weak_pointer_factory_(this) {
36 CardUnmaskPromptControllerImpl::~CardUnmaskPromptControllerImpl() {
37 if (card_unmask_view_
)
38 card_unmask_view_
->ControllerGone();
41 void CardUnmaskPromptControllerImpl::ShowPrompt(
42 const CreditCard
& card
,
43 base::WeakPtr
<CardUnmaskDelegate
> delegate
) {
44 if (card_unmask_view_
)
45 card_unmask_view_
->ControllerGone();
47 shown_timestamp_
= base::Time::Now();
48 pending_response_
= CardUnmaskDelegate::UnmaskResponse();
49 LoadRiskFingerprint();
52 card_unmask_view_
= CreateAndShowView();
53 unmasking_result_
= AutofillClient::NONE
;
54 unmasking_number_of_attempts_
= 0;
55 unmasking_initial_should_store_pan_
= GetStoreLocallyStartState();
56 AutofillMetrics::LogUnmaskPromptEvent(AutofillMetrics::UNMASK_PROMPT_SHOWN
);
59 bool CardUnmaskPromptControllerImpl::AllowsRetry(
60 AutofillClient::GetRealPanResult result
) {
61 if (result
== AutofillClient::NETWORK_ERROR
||
62 result
== AutofillClient::PERMANENT_FAILURE
) {
68 void CardUnmaskPromptControllerImpl::OnVerificationResult(
69 AutofillClient::GetRealPanResult result
) {
70 if (!card_unmask_view_
)
73 base::string16 error_message
;
75 case AutofillClient::SUCCESS
:
78 case AutofillClient::TRY_AGAIN_FAILURE
: {
79 error_message
= l10n_util::GetStringUTF16(
80 IDS_AUTOFILL_CARD_UNMASK_PROMPT_ERROR_TRY_AGAIN
);
84 case AutofillClient::PERMANENT_FAILURE
: {
85 error_message
= l10n_util::GetStringUTF16(
86 IDS_AUTOFILL_CARD_UNMASK_PROMPT_ERROR_PERMANENT
);
90 case AutofillClient::NETWORK_ERROR
: {
91 error_message
= l10n_util::GetStringUTF16(
92 IDS_AUTOFILL_CARD_UNMASK_PROMPT_ERROR_NETWORK
);
96 case AutofillClient::NONE
:
101 unmasking_result_
= result
;
102 AutofillMetrics::LogRealPanResult(result
);
103 AutofillMetrics::LogUnmaskingDuration(base::Time::Now() - verify_timestamp_
,
105 card_unmask_view_
->GotVerificationResult(error_message
,
106 AllowsRetry(result
));
109 void CardUnmaskPromptControllerImpl::OnUnmaskDialogClosed() {
110 card_unmask_view_
= nullptr;
112 delegate_
->OnUnmaskPromptClosed();
115 void CardUnmaskPromptControllerImpl::LogOnCloseEvents() {
116 AutofillMetrics::UnmaskPromptEvent close_reason_event
= GetCloseReasonEvent();
117 AutofillMetrics::LogUnmaskPromptEvent(close_reason_event
);
118 AutofillMetrics::LogUnmaskPromptEventDuration(
119 base::Time::Now() - shown_timestamp_
, close_reason_event
);
121 if (close_reason_event
== AutofillMetrics::UNMASK_PROMPT_CLOSED_NO_ATTEMPTS
)
124 if (close_reason_event
==
125 AutofillMetrics::UNMASK_PROMPT_CLOSED_ABANDON_UNMASKING
) {
126 AutofillMetrics::LogTimeBeforeAbandonUnmasking(base::Time::Now() -
130 bool final_should_store_pan
= pending_response_
.should_store_pan
;
131 if (unmasking_result_
== AutofillClient::SUCCESS
&& final_should_store_pan
) {
132 AutofillMetrics::LogUnmaskPromptEvent(
133 AutofillMetrics::UNMASK_PROMPT_SAVED_CARD_LOCALLY
);
136 if (CanStoreLocally()) {
137 // Tracking changes in local save preference.
138 AutofillMetrics::UnmaskPromptEvent event
;
139 if (unmasking_initial_should_store_pan_
&& final_should_store_pan
) {
140 event
= AutofillMetrics::UNMASK_PROMPT_LOCAL_SAVE_DID_NOT_OPT_OUT
;
141 } else if (!unmasking_initial_should_store_pan_
&&
142 !final_should_store_pan
) {
143 event
= AutofillMetrics::UNMASK_PROMPT_LOCAL_SAVE_DID_NOT_OPT_IN
;
144 } else if (unmasking_initial_should_store_pan_
&& !final_should_store_pan
) {
145 event
= AutofillMetrics::UNMASK_PROMPT_LOCAL_SAVE_DID_OPT_OUT
;
147 event
= AutofillMetrics::UNMASK_PROMPT_LOCAL_SAVE_DID_OPT_IN
;
149 AutofillMetrics::LogUnmaskPromptEvent(event
);
153 AutofillMetrics::UnmaskPromptEvent
154 CardUnmaskPromptControllerImpl::GetCloseReasonEvent() {
155 if (unmasking_number_of_attempts_
== 0)
156 return AutofillMetrics::UNMASK_PROMPT_CLOSED_NO_ATTEMPTS
;
158 // If NONE and we have a pending request, we have a pending GetRealPan
160 if (unmasking_result_
== AutofillClient::NONE
)
161 return AutofillMetrics::UNMASK_PROMPT_CLOSED_ABANDON_UNMASKING
;
163 if (unmasking_result_
== AutofillClient::SUCCESS
) {
164 return unmasking_number_of_attempts_
== 1
165 ? AutofillMetrics::UNMASK_PROMPT_UNMASKED_CARD_FIRST_ATTEMPT
167 UNMASK_PROMPT_UNMASKED_CARD_AFTER_FAILED_ATTEMPTS
;
169 return AllowsRetry(unmasking_result_
)
171 UNMASK_PROMPT_CLOSED_FAILED_TO_UNMASK_RETRIABLE_FAILURE
173 UNMASK_PROMPT_CLOSED_FAILED_TO_UNMASK_NON_RETRIABLE_FAILURE
;
177 void CardUnmaskPromptControllerImpl::OnUnmaskResponse(
178 const base::string16
& cvc
,
179 const base::string16
& exp_month
,
180 const base::string16
& exp_year
,
181 bool should_store_pan
) {
182 verify_timestamp_
= base::Time::Now();
183 unmasking_number_of_attempts_
++;
184 unmasking_result_
= AutofillClient::NONE
;
185 card_unmask_view_
->DisableAndWaitForVerification();
187 DCHECK(InputCvcIsValid(cvc
));
188 base::TrimWhitespace(cvc
, base::TRIM_ALL
, &pending_response_
.cvc
);
189 if (ShouldRequestExpirationDate()) {
190 DCHECK(InputExpirationIsValid(exp_month
, exp_year
));
191 pending_response_
.exp_month
= exp_month
;
192 pending_response_
.exp_year
= exp_year
;
194 if (CanStoreLocally()) {
195 pending_response_
.should_store_pan
= should_store_pan
;
197 DCHECK(!should_store_pan
);
198 pending_response_
.should_store_pan
= false;
201 // Remember the last choice the user made (on this device).
202 user_prefs::UserPrefs::Get(web_contents_
->GetBrowserContext())->SetBoolean(
203 prefs::kAutofillWalletImportStorageCheckboxState
, should_store_pan
);
205 if (!pending_response_
.risk_data
.empty())
206 delegate_
->OnUnmaskResponse(pending_response_
);
209 content::WebContents
* CardUnmaskPromptControllerImpl::GetWebContents() {
210 return web_contents_
;
213 base::string16
CardUnmaskPromptControllerImpl::GetWindowTitle() const {
214 int ids
= ShouldRequestExpirationDate()
215 ? IDS_AUTOFILL_CARD_UNMASK_PROMPT_UPDATE_TITLE
216 : IDS_AUTOFILL_CARD_UNMASK_PROMPT_TITLE
;
217 return l10n_util::GetStringFUTF16(ids
, card_
.TypeAndLastFourDigits());
220 base::string16
CardUnmaskPromptControllerImpl::GetInstructionsMessage() const {
221 if (ShouldRequestExpirationDate()) {
222 return l10n_util::GetStringUTF16(
223 card_
.type() == kAmericanExpressCard
224 ? IDS_AUTOFILL_CARD_UNMASK_PROMPT_INSTRUCTIONS_EXPIRED_AMEX
225 : IDS_AUTOFILL_CARD_UNMASK_PROMPT_INSTRUCTIONS_EXPIRED
);
228 return l10n_util::GetStringUTF16(
229 card_
.type() == kAmericanExpressCard
230 ? IDS_AUTOFILL_CARD_UNMASK_PROMPT_INSTRUCTIONS_AMEX
231 : IDS_AUTOFILL_CARD_UNMASK_PROMPT_INSTRUCTIONS
);
234 int CardUnmaskPromptControllerImpl::GetCvcImageRid() const {
235 return card_
.type() == kAmericanExpressCard
? IDR_CREDIT_CARD_CVC_HINT_AMEX
236 : IDR_CREDIT_CARD_CVC_HINT
;
239 bool CardUnmaskPromptControllerImpl::ShouldRequestExpirationDate() const {
240 return card_
.GetServerStatus() == CreditCard::EXPIRED
;
243 bool CardUnmaskPromptControllerImpl::CanStoreLocally() const {
244 // Never offer to save for incognito.
245 if (Profile::FromBrowserContext(web_contents_
->GetBrowserContext())
248 return autofill::OfferStoreUnmaskedCards();
251 bool CardUnmaskPromptControllerImpl::GetStoreLocallyStartState() const {
253 user_prefs::UserPrefs::Get(web_contents_
->GetBrowserContext());
254 return prefs
->GetBoolean(prefs::kAutofillWalletImportStorageCheckboxState
);
257 bool CardUnmaskPromptControllerImpl::InputCvcIsValid(
258 const base::string16
& input_text
) const {
259 base::string16 trimmed_text
;
260 base::TrimWhitespace(input_text
, base::TRIM_ALL
, &trimmed_text
);
261 size_t input_size
= card_
.type() == kAmericanExpressCard
? 4 : 3;
262 return trimmed_text
.size() == input_size
&&
263 base::ContainsOnlyChars(trimmed_text
,
264 base::ASCIIToUTF16("0123456789"));
267 bool CardUnmaskPromptControllerImpl::InputExpirationIsValid(
268 const base::string16
& month
,
269 const base::string16
& year
) const {
270 if ((month
.size() != 2U && month
.size() != 1U) ||
271 (year
.size() != 4U && year
.size() != 2U)) {
275 int month_value
= 0, year_value
= 0;
276 if (!base::StringToInt(month
, &month_value
) ||
277 !base::StringToInt(year
, &year_value
)) {
281 if (month_value
< 1 || month_value
> 12)
284 base::Time::Exploded now
;
285 base::Time::Now().LocalExplode(&now
);
287 // Convert 2 digit year to 4 digit year.
288 if (year_value
< 100)
289 year_value
+= (now
.year
/ 100) * 100;
291 if (year_value
< now
.year
)
294 if (year_value
> now
.year
)
297 return month_value
>= now
.month
;
300 base::TimeDelta
CardUnmaskPromptControllerImpl::GetSuccessMessageDuration()
302 return base::TimeDelta::FromMilliseconds(500);
305 CardUnmaskPromptView
* CardUnmaskPromptControllerImpl::CreateAndShowView() {
306 return CardUnmaskPromptView::CreateAndShow(this);
309 void CardUnmaskPromptControllerImpl::LoadRiskFingerprint() {
312 base::Bind(&CardUnmaskPromptControllerImpl::OnDidLoadRiskFingerprint
,
313 weak_pointer_factory_
.GetWeakPtr()));
316 void CardUnmaskPromptControllerImpl::OnDidLoadRiskFingerprint(
317 const std::string
& risk_data
) {
318 pending_response_
.risk_data
= risk_data
;
319 if (!pending_response_
.cvc
.empty())
320 delegate_
->OnUnmaskResponse(pending_response_
);
323 } // namespace autofill