OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #include "chrome/browser/ui/autofill/card_unmask_prompt_controller_impl.h" | |
6 | |
7 #include "base/bind.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/profiles/profile.h" | |
13 #include "chrome/browser/ui/autofill/card_unmask_prompt_view.h" | |
14 #include "chrome/grit/generated_resources.h" | |
15 #include "components/autofill/core/browser/autofill_experiments.h" | |
16 #include "components/autofill/core/browser/autofill_metrics.h" | |
17 #include "components/autofill/core/common/autofill_pref_names.h" | |
18 #include "grit/theme_resources.h" | |
19 #include "ui/base/l10n/l10n_util.h" | |
20 | |
21 namespace autofill { | |
22 | |
23 CardUnmaskPromptControllerImpl::CardUnmaskPromptControllerImpl( | |
24 content::WebContents* web_contents, | |
25 const RiskDataCallback& risk_data_callback, | |
26 PrefService* pref_service, | |
27 bool is_off_the_record) | |
28 : web_contents_(web_contents), | |
29 risk_data_callback_(risk_data_callback), | |
30 pref_service_(pref_service), | |
31 is_off_the_record_(is_off_the_record), | |
32 card_unmask_view_(nullptr), | |
33 unmasking_result_(AutofillClient::NONE), | |
34 unmasking_initial_should_store_pan_(false), | |
35 unmasking_number_of_attempts_(0), | |
36 weak_pointer_factory_(this) { | |
37 } | |
38 | |
39 CardUnmaskPromptControllerImpl::~CardUnmaskPromptControllerImpl() { | |
40 if (card_unmask_view_) | |
41 card_unmask_view_->ControllerGone(); | |
42 } | |
43 | |
44 void CardUnmaskPromptControllerImpl::ShowPrompt( | |
45 const CreditCard& card, | |
46 base::WeakPtr<CardUnmaskDelegate> delegate) { | |
47 if (card_unmask_view_) | |
48 card_unmask_view_->ControllerGone(); | |
49 | |
50 shown_timestamp_ = base::Time::Now(); | |
51 pending_response_ = CardUnmaskDelegate::UnmaskResponse(); | |
52 LoadRiskFingerprint(); | |
53 card_ = card; | |
54 delegate_ = delegate; | |
55 card_unmask_view_ = CreateAndShowView(); | |
56 unmasking_result_ = AutofillClient::NONE; | |
57 unmasking_number_of_attempts_ = 0; | |
58 unmasking_initial_should_store_pan_ = GetStoreLocallyStartState(); | |
59 AutofillMetrics::LogUnmaskPromptEvent(AutofillMetrics::UNMASK_PROMPT_SHOWN); | |
60 } | |
61 | |
62 bool CardUnmaskPromptControllerImpl::AllowsRetry( | |
63 AutofillClient::GetRealPanResult result) { | |
64 if (result == AutofillClient::NETWORK_ERROR || | |
65 result == AutofillClient::PERMANENT_FAILURE) { | |
66 return false; | |
67 } | |
68 return true; | |
69 } | |
70 | |
71 void CardUnmaskPromptControllerImpl::OnVerificationResult( | |
72 AutofillClient::GetRealPanResult result) { | |
73 if (!card_unmask_view_) | |
74 return; | |
75 | |
76 base::string16 error_message; | |
77 switch (result) { | |
78 case AutofillClient::SUCCESS: | |
79 break; | |
80 | |
81 case AutofillClient::TRY_AGAIN_FAILURE: { | |
82 error_message = l10n_util::GetStringUTF16( | |
83 IDS_AUTOFILL_CARD_UNMASK_PROMPT_ERROR_TRY_AGAIN); | |
84 break; | |
85 } | |
86 | |
87 case AutofillClient::PERMANENT_FAILURE: { | |
88 error_message = l10n_util::GetStringUTF16( | |
89 IDS_AUTOFILL_CARD_UNMASK_PROMPT_ERROR_PERMANENT); | |
90 break; | |
91 } | |
92 | |
93 case AutofillClient::NETWORK_ERROR: { | |
94 error_message = l10n_util::GetStringUTF16( | |
95 IDS_AUTOFILL_CARD_UNMASK_PROMPT_ERROR_NETWORK); | |
96 break; | |
97 } | |
98 | |
99 case AutofillClient::NONE: | |
100 NOTREACHED(); | |
101 return; | |
102 } | |
103 | |
104 unmasking_result_ = result; | |
105 AutofillMetrics::LogRealPanResult(result); | |
106 AutofillMetrics::LogUnmaskingDuration(base::Time::Now() - verify_timestamp_, | |
107 result); | |
108 card_unmask_view_->GotVerificationResult(error_message, | |
109 AllowsRetry(result)); | |
110 } | |
111 | |
112 void CardUnmaskPromptControllerImpl::OnUnmaskDialogClosed() { | |
113 card_unmask_view_ = nullptr; | |
114 LogOnCloseEvents(); | |
115 if (delegate_.get()) | |
116 delegate_->OnUnmaskPromptClosed(); | |
117 } | |
118 | |
119 void CardUnmaskPromptControllerImpl::LogOnCloseEvents() { | |
120 AutofillMetrics::UnmaskPromptEvent close_reason_event = GetCloseReasonEvent(); | |
121 AutofillMetrics::LogUnmaskPromptEvent(close_reason_event); | |
122 AutofillMetrics::LogUnmaskPromptEventDuration( | |
123 base::Time::Now() - shown_timestamp_, close_reason_event); | |
124 | |
125 if (close_reason_event == AutofillMetrics::UNMASK_PROMPT_CLOSED_NO_ATTEMPTS) | |
126 return; | |
127 | |
128 if (close_reason_event == | |
129 AutofillMetrics::UNMASK_PROMPT_CLOSED_ABANDON_UNMASKING) { | |
130 AutofillMetrics::LogTimeBeforeAbandonUnmasking(base::Time::Now() - | |
131 verify_timestamp_); | |
132 } | |
133 | |
134 bool final_should_store_pan = pending_response_.should_store_pan; | |
135 if (unmasking_result_ == AutofillClient::SUCCESS && final_should_store_pan) { | |
136 AutofillMetrics::LogUnmaskPromptEvent( | |
137 AutofillMetrics::UNMASK_PROMPT_SAVED_CARD_LOCALLY); | |
138 } | |
139 | |
140 if (CanStoreLocally()) { | |
141 // Tracking changes in local save preference. | |
142 AutofillMetrics::UnmaskPromptEvent event; | |
143 if (unmasking_initial_should_store_pan_ && final_should_store_pan) { | |
144 event = AutofillMetrics::UNMASK_PROMPT_LOCAL_SAVE_DID_NOT_OPT_OUT; | |
145 } else if (!unmasking_initial_should_store_pan_ && | |
146 !final_should_store_pan) { | |
147 event = AutofillMetrics::UNMASK_PROMPT_LOCAL_SAVE_DID_NOT_OPT_IN; | |
148 } else if (unmasking_initial_should_store_pan_ && !final_should_store_pan) { | |
149 event = AutofillMetrics::UNMASK_PROMPT_LOCAL_SAVE_DID_OPT_OUT; | |
150 } else { | |
151 event = AutofillMetrics::UNMASK_PROMPT_LOCAL_SAVE_DID_OPT_IN; | |
152 } | |
153 AutofillMetrics::LogUnmaskPromptEvent(event); | |
154 } | |
155 } | |
156 | |
157 AutofillMetrics::UnmaskPromptEvent | |
158 CardUnmaskPromptControllerImpl::GetCloseReasonEvent() { | |
159 if (unmasking_number_of_attempts_ == 0) | |
160 return AutofillMetrics::UNMASK_PROMPT_CLOSED_NO_ATTEMPTS; | |
161 | |
162 // If NONE and we have a pending request, we have a pending GetRealPan | |
163 // request. | |
164 if (unmasking_result_ == AutofillClient::NONE) | |
165 return AutofillMetrics::UNMASK_PROMPT_CLOSED_ABANDON_UNMASKING; | |
166 | |
167 if (unmasking_result_ == AutofillClient::SUCCESS) { | |
168 return unmasking_number_of_attempts_ == 1 | |
169 ? AutofillMetrics::UNMASK_PROMPT_UNMASKED_CARD_FIRST_ATTEMPT | |
170 : AutofillMetrics:: | |
171 UNMASK_PROMPT_UNMASKED_CARD_AFTER_FAILED_ATTEMPTS; | |
172 } else { | |
173 return AllowsRetry(unmasking_result_) | |
174 ? AutofillMetrics:: | |
175 UNMASK_PROMPT_CLOSED_FAILED_TO_UNMASK_RETRIABLE_FAILURE | |
176 : AutofillMetrics:: | |
177 UNMASK_PROMPT_CLOSED_FAILED_TO_UNMASK_NON_RETRIABLE_FAILURE
; | |
178 } | |
179 } | |
180 | |
181 void CardUnmaskPromptControllerImpl::OnUnmaskResponse( | |
182 const base::string16& cvc, | |
183 const base::string16& exp_month, | |
184 const base::string16& exp_year, | |
185 bool should_store_pan) { | |
186 verify_timestamp_ = base::Time::Now(); | |
187 unmasking_number_of_attempts_++; | |
188 unmasking_result_ = AutofillClient::NONE; | |
189 card_unmask_view_->DisableAndWaitForVerification(); | |
190 | |
191 DCHECK(InputCvcIsValid(cvc)); | |
192 base::TrimWhitespace(cvc, base::TRIM_ALL, &pending_response_.cvc); | |
193 if (ShouldRequestExpirationDate()) { | |
194 DCHECK(InputExpirationIsValid(exp_month, exp_year)); | |
195 pending_response_.exp_month = exp_month; | |
196 pending_response_.exp_year = exp_year; | |
197 } | |
198 if (CanStoreLocally()) { | |
199 pending_response_.should_store_pan = should_store_pan; | |
200 // Remember the last choice the user made (on this device). | |
201 pref_service_->SetBoolean( | |
202 prefs::kAutofillWalletImportStorageCheckboxState, should_store_pan); | |
203 } else { | |
204 DCHECK(!should_store_pan); | |
205 pending_response_.should_store_pan = false; | |
206 } | |
207 | |
208 if (!pending_response_.risk_data.empty()) | |
209 delegate_->OnUnmaskResponse(pending_response_); | |
210 } | |
211 | |
212 content::WebContents* CardUnmaskPromptControllerImpl::GetWebContents() { | |
213 return web_contents_; | |
214 } | |
215 | |
216 base::string16 CardUnmaskPromptControllerImpl::GetWindowTitle() const { | |
217 int ids = ShouldRequestExpirationDate() | |
218 ? IDS_AUTOFILL_CARD_UNMASK_PROMPT_UPDATE_TITLE | |
219 : IDS_AUTOFILL_CARD_UNMASK_PROMPT_TITLE; | |
220 return l10n_util::GetStringFUTF16(ids, card_.TypeAndLastFourDigits()); | |
221 } | |
222 | |
223 base::string16 CardUnmaskPromptControllerImpl::GetInstructionsMessage() const { | |
224 if (ShouldRequestExpirationDate()) { | |
225 return l10n_util::GetStringUTF16( | |
226 card_.type() == kAmericanExpressCard | |
227 ? IDS_AUTOFILL_CARD_UNMASK_PROMPT_INSTRUCTIONS_EXPIRED_AMEX | |
228 : IDS_AUTOFILL_CARD_UNMASK_PROMPT_INSTRUCTIONS_EXPIRED); | |
229 } | |
230 | |
231 return l10n_util::GetStringUTF16( | |
232 card_.type() == kAmericanExpressCard | |
233 ? IDS_AUTOFILL_CARD_UNMASK_PROMPT_INSTRUCTIONS_AMEX | |
234 : IDS_AUTOFILL_CARD_UNMASK_PROMPT_INSTRUCTIONS); | |
235 } | |
236 | |
237 int CardUnmaskPromptControllerImpl::GetCvcImageRid() const { | |
238 return card_.type() == kAmericanExpressCard ? IDR_CREDIT_CARD_CVC_HINT_AMEX | |
239 : IDR_CREDIT_CARD_CVC_HINT; | |
240 } | |
241 | |
242 bool CardUnmaskPromptControllerImpl::ShouldRequestExpirationDate() const { | |
243 return card_.GetServerStatus() == CreditCard::EXPIRED; | |
244 } | |
245 | |
246 bool CardUnmaskPromptControllerImpl::CanStoreLocally() const { | |
247 // Never offer to save for incognito. | |
248 if (is_off_the_record_) | |
249 return false; | |
250 return autofill::OfferStoreUnmaskedCards(); | |
251 } | |
252 | |
253 bool CardUnmaskPromptControllerImpl::GetStoreLocallyStartState() const { | |
254 return pref_service_->GetBoolean( | |
255 prefs::kAutofillWalletImportStorageCheckboxState); | |
256 } | |
257 | |
258 bool CardUnmaskPromptControllerImpl::InputCvcIsValid( | |
259 const base::string16& input_text) const { | |
260 base::string16 trimmed_text; | |
261 base::TrimWhitespace(input_text, base::TRIM_ALL, &trimmed_text); | |
262 size_t input_size = card_.type() == kAmericanExpressCard ? 4 : 3; | |
263 return trimmed_text.size() == input_size && | |
264 base::ContainsOnlyChars(trimmed_text, | |
265 base::ASCIIToUTF16("0123456789")); | |
266 } | |
267 | |
268 bool CardUnmaskPromptControllerImpl::InputExpirationIsValid( | |
269 const base::string16& month, | |
270 const base::string16& year) const { | |
271 if ((month.size() != 2U && month.size() != 1U) || | |
272 (year.size() != 4U && year.size() != 2U)) { | |
273 return false; | |
274 } | |
275 | |
276 int month_value = 0, year_value = 0; | |
277 if (!base::StringToInt(month, &month_value) || | |
278 !base::StringToInt(year, &year_value)) { | |
279 return false; | |
280 } | |
281 | |
282 if (month_value < 1 || month_value > 12) | |
283 return false; | |
284 | |
285 base::Time::Exploded now; | |
286 base::Time::Now().LocalExplode(&now); | |
287 | |
288 // Convert 2 digit year to 4 digit year. | |
289 if (year_value < 100) | |
290 year_value += (now.year / 100) * 100; | |
291 | |
292 if (year_value < now.year) | |
293 return false; | |
294 | |
295 if (year_value > now.year) | |
296 return true; | |
297 | |
298 return month_value >= now.month; | |
299 } | |
300 | |
301 base::TimeDelta CardUnmaskPromptControllerImpl::GetSuccessMessageDuration() | |
302 const { | |
303 return base::TimeDelta::FromMilliseconds(500); | |
304 } | |
305 | |
306 CardUnmaskPromptView* CardUnmaskPromptControllerImpl::CreateAndShowView() { | |
307 return CardUnmaskPromptView::CreateAndShow(this); | |
308 } | |
309 | |
310 void CardUnmaskPromptControllerImpl::LoadRiskFingerprint() { | |
311 risk_data_callback_.Run( | |
312 base::Bind(&CardUnmaskPromptControllerImpl::OnDidLoadRiskFingerprint, | |
313 weak_pointer_factory_.GetWeakPtr())); | |
314 } | |
315 | |
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_); | |
321 } | |
322 | |
323 } // namespace autofill | |
OLD | NEW |