1 // Copyright 2013 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 "components/autofill/content/browser/wallet/wallet_items.h"
6
7 #include "base/logging.h"
8 #include "base/strings/string_number_conversions.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "base/values.h"
11 #include "components/autofill/content/browser/wallet/gaia_account.h"
12 #include "components/autofill/core/browser/autofill_type.h"
13 #include "components/autofill/core/browser/credit_card.h"
14 #include "grit/component_strings.h"
15 #include "grit/webkit_resources.h"
16 #include "ui/base/l10n/l10n_util.h"
17 #include "ui/base/resource/resource_bundle.h"
18 #include "ui/gfx/image/image.h"
19 #include "url/gurl.h"
20
21 namespace autofill {
22 namespace wallet {
23
24 namespace {
25
26 const char kLegalDocumentUrl[] =
27 "https://wallet.google.com/legaldocument?docId=";
28 const char kPrivacyNoticeUrl[] = "https://wallet.google.com/files/privacy.html";
29
30 // TODO(estade): move to base/.
31 template<class T>
VectorsAreEqual(const std::vector<T * > & a,const std::vector<T * > & b)32 bool VectorsAreEqual(const std::vector<T*>& a, const std::vector<T*>& b) {
33 if (a.size() != b.size())
34 return false;
35
36 for (size_t i = 0; i < a.size(); ++i) {
37 if (*a[i] != *b[i])
38 return false;
39 }
40
41 return true;
42 }
43
44 WalletItems::MaskedInstrument::Type
TypeFromString(const std::string & type_string)45 TypeFromString(const std::string& type_string) {
46 if (type_string == "VISA")
47 return WalletItems::MaskedInstrument::VISA;
48 if (type_string == "MASTER_CARD")
49 return WalletItems::MaskedInstrument::MASTER_CARD;
50 if (type_string == "AMEX")
51 return WalletItems::MaskedInstrument::AMEX;
52 if (type_string == "DISCOVER")
53 return WalletItems::MaskedInstrument::DISCOVER;
54 if (type_string == "SOLO")
55 return WalletItems::MaskedInstrument::SOLO;
56 if (type_string == "MAESTRO")
57 return WalletItems::MaskedInstrument::MAESTRO;
58 if (type_string == "SWITCH")
59 return WalletItems::MaskedInstrument::SWITCH;
60 return WalletItems::MaskedInstrument::UNKNOWN;
61 }
62
63 WalletItems::MaskedInstrument::Status
StatusFromString(const std::string & status_string)64 StatusFromString(const std::string& status_string) {
65 if (status_string == "AMEX_NOT_SUPPORTED")
66 return WalletItems::MaskedInstrument::AMEX_NOT_SUPPORTED;
67 if (status_string == "PENDING")
68 return WalletItems::MaskedInstrument::PENDING;
69 if (status_string == "VALID")
70 return WalletItems::MaskedInstrument::VALID;
71 if (status_string == "DECLINED")
72 return WalletItems::MaskedInstrument::DECLINED;
73 if (status_string == "DISABLED_FOR_THIS_MERCHANT")
74 return WalletItems::MaskedInstrument::DISABLED_FOR_THIS_MERCHANT;
75 if (status_string == "UNSUPPORTED_COUNTRY")
76 return WalletItems::MaskedInstrument::UNSUPPORTED_COUNTRY;
77 if (status_string == "EXPIRED")
78 return WalletItems::MaskedInstrument::EXPIRED;
79 if (status_string == "BILLING_INCOMPLETE")
80 return WalletItems::MaskedInstrument::BILLING_INCOMPLETE;
81 return WalletItems::MaskedInstrument::INAPPLICABLE;
82 }
83
DisplayStringFromType(WalletItems::MaskedInstrument::Type type)84 base::string16 DisplayStringFromType(WalletItems::MaskedInstrument::Type type) {
85 switch (type) {
86 case WalletItems::MaskedInstrument::AMEX:
87 return CreditCard::TypeForDisplay(kAmericanExpressCard);
88 case WalletItems::MaskedInstrument::DISCOVER:
89 return CreditCard::TypeForDisplay(kDiscoverCard);
90 case WalletItems::MaskedInstrument::MASTER_CARD:
91 return CreditCard::TypeForDisplay(kMasterCard);
92 case WalletItems::MaskedInstrument::VISA:
93 return CreditCard::TypeForDisplay(kVisaCard);
94 default:
95 return CreditCard::TypeForDisplay(kGenericCard);
96 }
97 }
98
99 } // anonymous namespace
100
MaskedInstrument(const base::string16 & descriptive_name,const WalletItems::MaskedInstrument::Type & type,const std::vector<base::string16> & supported_currencies,const base::string16 & last_four_digits,int expiration_month,int expiration_year,scoped_ptr<Address> address,const WalletItems::MaskedInstrument::Status & status,const std::string & object_id)101 WalletItems::MaskedInstrument::MaskedInstrument(
102 const base::string16& descriptive_name,
103 const WalletItems::MaskedInstrument::Type& type,
104 const std::vector<base::string16>& supported_currencies,
105 const base::string16& last_four_digits,
106 int expiration_month,
107 int expiration_year,
108 scoped_ptr<Address> address,
109 const WalletItems::MaskedInstrument::Status& status,
110 const std::string& object_id)
111 : descriptive_name_(descriptive_name),
112 type_(type),
113 supported_currencies_(supported_currencies),
114 last_four_digits_(last_four_digits),
115 expiration_month_(expiration_month),
116 expiration_year_(expiration_year),
117 address_(address.Pass()),
118 status_(status),
119 object_id_(object_id) {
120 DCHECK(address_);
121 }
122
~MaskedInstrument()123 WalletItems::MaskedInstrument::~MaskedInstrument() {}
124
125 scoped_ptr<WalletItems::MaskedInstrument>
CreateMaskedInstrument(const base::DictionaryValue & dictionary)126 WalletItems::MaskedInstrument::CreateMaskedInstrument(
127 const base::DictionaryValue& dictionary) {
128 std::string type_string;
129 Type type;
130 if (dictionary.GetString("type", &type_string)) {
131 type = TypeFromString(type_string);
132 } else {
133 DLOG(ERROR) << "Response from Google Wallet missing card type";
134 return scoped_ptr<MaskedInstrument>();
135 }
136
137 base::string16 last_four_digits;
138 if (!dictionary.GetString("last_four_digits", &last_four_digits)) {
139 DLOG(ERROR) << "Response from Google Wallet missing last four digits";
140 return scoped_ptr<MaskedInstrument>();
141 }
142
143 std::string status_string;
144 Status status;
145 if (dictionary.GetString("status", &status_string)) {
146 status = StatusFromString(status_string);
147 } else {
148 DLOG(ERROR) << "Response from Google Wallet missing status";
149 return scoped_ptr<MaskedInstrument>();
150 }
151
152 std::string object_id;
153 if (!dictionary.GetString("object_id", &object_id)) {
154 DLOG(ERROR) << "Response from Google Wallet missing object id";
155 return scoped_ptr<MaskedInstrument>();
156 }
157
158 const DictionaryValue* address_dict;
159 if (!dictionary.GetDictionary("billing_address", &address_dict)) {
160 DLOG(ERROR) << "Response from Google wallet missing address";
161 return scoped_ptr<MaskedInstrument>();
162 }
163 scoped_ptr<Address> address = Address::CreateDisplayAddress(*address_dict);
164
165 if (!address) {
166 DLOG(ERROR) << "Response from Google wallet contained malformed address";
167 return scoped_ptr<MaskedInstrument>();
168 }
169
170 std::vector<base::string16> supported_currencies;
171 const ListValue* supported_currency_list;
172 if (dictionary.GetList("supported_currency", &supported_currency_list)) {
173 for (size_t i = 0; i < supported_currency_list->GetSize(); ++i) {
174 base::string16 currency;
175 if (supported_currency_list->GetString(i, ¤cy))
176 supported_currencies.push_back(currency);
177 }
178 } else {
179 DVLOG(1) << "Response from Google Wallet missing supported currency";
180 }
181
182 int expiration_month;
183 if (!dictionary.GetInteger("expiration_month", &expiration_month))
184 DVLOG(1) << "Response from Google Wallet missing expiration month";
185
186 int expiration_year;
187 if (!dictionary.GetInteger("expiration_year", &expiration_year))
188 DVLOG(1) << "Response from Google Wallet missing expiration year";
189
190 base::string16 descriptive_name;
191 if (!dictionary.GetString("descriptive_name", &descriptive_name))
192 DVLOG(1) << "Response from Google Wallet missing descriptive name";
193
194 return scoped_ptr<MaskedInstrument>(new MaskedInstrument(descriptive_name,
195 type,
196 supported_currencies,
197 last_four_digits,
198 expiration_month,
199 expiration_year,
200 address.Pass(),
201 status,
202 object_id));
203 }
204
operator ==(const WalletItems::MaskedInstrument & other) const205 bool WalletItems::MaskedInstrument::operator==(
206 const WalletItems::MaskedInstrument& other) const {
207 if (descriptive_name_ != other.descriptive_name_)
208 return false;
209 if (type_ != other.type_)
210 return false;
211 if (supported_currencies_ != other.supported_currencies_)
212 return false;
213 if (last_four_digits_ != other.last_four_digits_)
214 return false;
215 if (expiration_month_ != other.expiration_month_)
216 return false;
217 if (expiration_year_ != other.expiration_year_)
218 return false;
219 if (address_) {
220 if (other.address_) {
221 if (*address_ != *other.address_)
222 return false;
223 } else {
224 return false;
225 }
226 } else if (other.address_) {
227 return false;
228 }
229 if (status_ != other.status_)
230 return false;
231 if (object_id_ != other.object_id_)
232 return false;
233 return true;
234 }
235
operator !=(const WalletItems::MaskedInstrument & other) const236 bool WalletItems::MaskedInstrument::operator!=(
237 const WalletItems::MaskedInstrument& other) const {
238 return !(*this == other);
239 }
240
GetInstrumentById(const std::string & object_id) const241 const WalletItems::MaskedInstrument* WalletItems::GetInstrumentById(
242 const std::string& object_id) const {
243 if (object_id.empty())
244 return NULL;
245
246 for (size_t i = 0; i < instruments_.size(); ++i) {
247 if (instruments_[i]->object_id() == object_id)
248 return instruments_[i];
249 }
250
251 return NULL;
252 }
253
HasRequiredAction(RequiredAction action) const254 bool WalletItems::HasRequiredAction(RequiredAction action) const {
255 DCHECK(ActionAppliesToWalletItems(action));
256 return std::find(required_actions_.begin(),
257 required_actions_.end(),
258 action) != required_actions_.end();
259 }
260
SupportsCard(const base::string16 & card_number,base::string16 * message) const261 bool WalletItems::SupportsCard(const base::string16& card_number,
262 base::string16* message) const {
263 std::string card_type = CreditCard::GetCreditCardType(card_number);
264
265 if (card_type == kVisaCard ||
266 card_type == kMasterCard ||
267 card_type == kDiscoverCard) {
268 return true;
269 }
270
271 if (card_type == kAmericanExpressCard) {
272 if (amex_permission_ == AMEX_ALLOWED)
273 return true;
274
275 *message = l10n_util::GetStringUTF16(
276 IDS_AUTOFILL_CREDIT_CARD_NOT_SUPPORTED_BY_WALLET_FOR_MERCHANT);
277 return false;
278 }
279
280 *message = l10n_util::GetStringUTF16(
281 IDS_AUTOFILL_CREDIT_CARD_NOT_SUPPORTED_BY_WALLET);
282 return false;
283 }
284
ObfuscatedGaiaId() const285 std::string WalletItems::ObfuscatedGaiaId() const {
286 if (active_account_index_ >= gaia_accounts_.size())
287 return std::string();
288
289 return gaia_accounts_[active_account_index_]->obfuscated_id();
290 }
291
DisplayName() const292 base::string16 WalletItems::MaskedInstrument::DisplayName() const {
293 #if defined(OS_ANDROID)
294 // TODO(aruslan): improve this stub implementation.
295 return descriptive_name();
296 #else
297 return descriptive_name();
298 #endif
299 }
300
DisplayNameDetail() const301 base::string16 WalletItems::MaskedInstrument::DisplayNameDetail() const {
302 #if defined(OS_ANDROID)
303 // TODO(aruslan): improve this stub implementation.
304 return address().DisplayName();
305 #else
306 return base::string16();
307 #endif
308 }
309
TypeAndLastFourDigits() const310 base::string16 WalletItems::MaskedInstrument::TypeAndLastFourDigits() const {
311 // TODO(dbeam): i18n.
312 return DisplayStringFromType(type_) + ASCIIToUTF16(" - ") +
313 last_four_digits();
314 }
315
CardIcon() const316 const gfx::Image& WalletItems::MaskedInstrument::CardIcon() const {
317 int idr = 0;
318 switch (type_) {
319 case AMEX:
320 idr = IDR_AUTOFILL_CC_AMEX;
321 break;
322
323 case DISCOVER:
324 idr = IDR_AUTOFILL_CC_DISCOVER;
325 break;
326
327 case MASTER_CARD:
328 idr = IDR_AUTOFILL_CC_MASTERCARD;
329 break;
330
331 case VISA:
332 idr = IDR_AUTOFILL_CC_VISA;
333 break;
334
335 case SOLO:
336 case MAESTRO:
337 case SWITCH:
338 case UNKNOWN:
339 idr = IDR_AUTOFILL_CC_GENERIC;
340 break;
341 }
342
343 return ResourceBundle::GetSharedInstance().GetImageNamed(idr);
344 }
345
GetInfo(const AutofillType & type,const std::string & app_locale) const346 base::string16 WalletItems::MaskedInstrument::GetInfo(
347 const AutofillType& type,
348 const std::string& app_locale) const {
349 if (type.group() != CREDIT_CARD)
350 return address().GetInfo(type, app_locale);
351
352 switch (type.GetStorableType()) {
353 case CREDIT_CARD_NAME:
354 return address().recipient_name();
355
356 case CREDIT_CARD_NUMBER:
357 return DisplayName();
358
359 case CREDIT_CARD_EXP_4_DIGIT_YEAR:
360 return base::IntToString16(expiration_year());
361
362 case CREDIT_CARD_VERIFICATION_CODE:
363 break;
364
365 case CREDIT_CARD_TYPE:
366 return DisplayStringFromType(type_);
367
368 default:
369 NOTREACHED();
370 }
371
372 return base::string16();
373 }
374
~LegalDocument()375 WalletItems::LegalDocument::~LegalDocument() {}
376
377 scoped_ptr<WalletItems::LegalDocument>
CreateLegalDocument(const base::DictionaryValue & dictionary)378 WalletItems::LegalDocument::CreateLegalDocument(
379 const base::DictionaryValue& dictionary) {
380 std::string id;
381 if (!dictionary.GetString("legal_document_id", &id)) {
382 DLOG(ERROR) << "Response from Google Wallet missing legal document id";
383 return scoped_ptr<LegalDocument>();
384 }
385
386 base::string16 display_name;
387 if (!dictionary.GetString("display_name", &display_name)) {
388 DLOG(ERROR) << "Response from Google Wallet missing display name";
389 return scoped_ptr<LegalDocument>();
390 }
391
392 return scoped_ptr<LegalDocument>(new LegalDocument(id, display_name));
393 }
394
395 scoped_ptr<WalletItems::LegalDocument>
CreatePrivacyPolicyDocument()396 WalletItems::LegalDocument::CreatePrivacyPolicyDocument() {
397 return scoped_ptr<LegalDocument>(new LegalDocument(
398 GURL(kPrivacyNoticeUrl),
399 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_PRIVACY_POLICY_LINK)));
400 }
401
operator ==(const LegalDocument & other) const402 bool WalletItems::LegalDocument::operator==(const LegalDocument& other) const {
403 return id_ == other.id_ &&
404 url_ == other.url_ &&
405 display_name_ == other.display_name_;
406 }
407
operator !=(const LegalDocument & other) const408 bool WalletItems::LegalDocument::operator!=(const LegalDocument& other) const {
409 return !(*this == other);
410 }
411
LegalDocument(const std::string & id,const base::string16 & display_name)412 WalletItems::LegalDocument::LegalDocument(const std::string& id,
413 const base::string16& display_name)
414 : id_(id),
415 url_(kLegalDocumentUrl + id),
416 display_name_(display_name) {}
417
LegalDocument(const GURL & url,const base::string16 & display_name)418 WalletItems::LegalDocument::LegalDocument(const GURL& url,
419 const base::string16& display_name)
420 : url_(url),
421 display_name_(display_name) {}
422
WalletItems(const std::vector<RequiredAction> & required_actions,const std::string & google_transaction_id,const std::string & default_instrument_id,const std::string & default_address_id,AmexPermission amex_permission)423 WalletItems::WalletItems(const std::vector<RequiredAction>& required_actions,
424 const std::string& google_transaction_id,
425 const std::string& default_instrument_id,
426 const std::string& default_address_id,
427 AmexPermission amex_permission)
428 : required_actions_(required_actions),
429 google_transaction_id_(google_transaction_id),
430 default_instrument_id_(default_instrument_id),
431 default_address_id_(default_address_id),
432 active_account_index_(std::numeric_limits<size_t>::max()),
433 amex_permission_(amex_permission) {}
434
~WalletItems()435 WalletItems::~WalletItems() {}
436
437 scoped_ptr<WalletItems>
CreateWalletItems(const base::DictionaryValue & dictionary)438 WalletItems::CreateWalletItems(const base::DictionaryValue& dictionary) {
439 std::vector<RequiredAction> required_action;
440 const ListValue* required_action_list;
441 if (dictionary.GetList("required_action", &required_action_list)) {
442 for (size_t i = 0; i < required_action_list->GetSize(); ++i) {
443 std::string action_string;
444 if (required_action_list->GetString(i, &action_string)) {
445 RequiredAction action = ParseRequiredActionFromString(action_string);
446 if (!ActionAppliesToWalletItems(action)) {
447 DLOG(ERROR) << "Response from Google wallet with bad required action:"
448 " \"" << action_string << "\"";
449 return scoped_ptr<WalletItems>();
450 }
451 required_action.push_back(action);
452 }
453 }
454 } else {
455 DVLOG(1) << "Response from Google wallet missing required actions";
456 }
457
458 std::string google_transaction_id;
459 if (!dictionary.GetString("google_transaction_id", &google_transaction_id) &&
460 required_action.empty()) {
461 DLOG(ERROR) << "Response from Google wallet missing google transaction id";
462 return scoped_ptr<WalletItems>();
463 }
464
465 std::string default_instrument_id;
466 if (!dictionary.GetString("default_instrument_id", &default_instrument_id))
467 DVLOG(1) << "Response from Google wallet missing default instrument id";
468
469 std::string default_address_id;
470 if (!dictionary.GetString("default_address_id", &default_address_id))
471 DVLOG(1) << "Response from Google wallet missing default_address_id";
472
473 // obfuscated_gaia_id is deprecated.
474
475 bool amex_disallowed = true;
476 if (!dictionary.GetBoolean("amex_disallowed", &amex_disallowed))
477 DVLOG(1) << "Response from Google wallet missing the amex_disallowed field";
478 AmexPermission amex_permission =
479 amex_disallowed ? AMEX_DISALLOWED : AMEX_ALLOWED;
480
481 scoped_ptr<WalletItems> wallet_items(new WalletItems(required_action,
482 google_transaction_id,
483 default_instrument_id,
484 default_address_id,
485 amex_permission));
486 std::vector<std::string> gaia_accounts;
487 const base::ListValue* gaia_profiles;
488 if (dictionary.GetList("gaia_profile", &gaia_profiles)) {
489 for (size_t i = 0; i < gaia_profiles->GetSize(); ++i) {
490 const base::DictionaryValue* account_dict;
491 std::string email;
492 if (!gaia_profiles->GetDictionary(i, &account_dict))
493 continue;
494
495 scoped_ptr<GaiaAccount> gaia_account(
496 GaiaAccount::Create(*account_dict));
497 if (gaia_account)
498 wallet_items->AddAccount(gaia_account.Pass());
499 }
500 } else {
501 DVLOG(1) << "Response from Google wallet missing GAIA accounts";
502 }
503
504 const ListValue* legal_docs;
505 if (dictionary.GetList("required_legal_document", &legal_docs)) {
506 for (size_t i = 0; i < legal_docs->GetSize(); ++i) {
507 const DictionaryValue* legal_doc_dict;
508 if (legal_docs->GetDictionary(i, &legal_doc_dict)) {
509 scoped_ptr<LegalDocument> legal_doc(
510 LegalDocument::CreateLegalDocument(*legal_doc_dict));
511 if (legal_doc)
512 wallet_items->AddLegalDocument(legal_doc.Pass());
513 else
514 return scoped_ptr<WalletItems>();
515 }
516 }
517
518 if (!legal_docs->empty()) {
519 // Always append the privacy policy link as well.
520 wallet_items->AddLegalDocument(
521 LegalDocument::CreatePrivacyPolicyDocument());
522 }
523 } else {
524 DVLOG(1) << "Response from Google wallet missing legal docs";
525 }
526
527 const ListValue* instruments;
528 if (dictionary.GetList("instrument", &instruments)) {
529 for (size_t i = 0; i < instruments->GetSize(); ++i) {
530 const DictionaryValue* instrument_dict;
531 if (instruments->GetDictionary(i, &instrument_dict)) {
532 scoped_ptr<MaskedInstrument> instrument(
533 MaskedInstrument::CreateMaskedInstrument(*instrument_dict));
534 if (instrument)
535 wallet_items->AddInstrument(instrument.Pass());
536 }
537 }
538 } else {
539 DVLOG(1) << "Response from Google wallet missing instruments";
540 }
541
542 const ListValue* addresses;
543 if (dictionary.GetList("address", &addresses)) {
544 for (size_t i = 0; i < addresses->GetSize(); ++i) {
545 const DictionaryValue* address_dict;
546 if (addresses->GetDictionary(i, &address_dict)) {
547 scoped_ptr<Address> address(
548 Address::CreateAddressWithID(*address_dict));
549 if (address)
550 wallet_items->AddAddress(address.Pass());
551 }
552 }
553 } else {
554 DVLOG(1) << "Response from Google wallet missing addresses";
555 }
556
557 return wallet_items.Pass();
558 }
559
AddAccount(scoped_ptr<GaiaAccount> account)560 void WalletItems::AddAccount(scoped_ptr<GaiaAccount> account) {
561 if (account->index() != gaia_accounts_.size()) {
562 DVLOG(1) << "Tried to add account out of order";
563 return;
564 }
565
566 if (account->is_active())
567 active_account_index_ = account->index();
568
569 gaia_accounts_.push_back(account.release());
570 }
571
operator ==(const WalletItems & other) const572 bool WalletItems::operator==(const WalletItems& other) const {
573 return google_transaction_id_ == other.google_transaction_id_ &&
574 default_instrument_id_ == other.default_instrument_id_ &&
575 default_address_id_ == other.default_address_id_ &&
576 required_actions_ == other.required_actions_ &&
577 // This check is technically redundant, but is useful for tests.
578 ObfuscatedGaiaId() == other.ObfuscatedGaiaId() &&
579 active_account_index() == other.active_account_index() &&
580 VectorsAreEqual<GaiaAccount>(gaia_accounts(),
581 other.gaia_accounts()) &&
582 VectorsAreEqual<MaskedInstrument>(instruments(),
583 other.instruments()) &&
584 VectorsAreEqual<Address>(addresses(), other.addresses()) &&
585 VectorsAreEqual<LegalDocument>(legal_documents(),
586 other.legal_documents());
587 }
588
operator !=(const WalletItems & other) const589 bool WalletItems::operator!=(const WalletItems& other) const {
590 return !(*this == other);
591 }
592
593 } // namespace wallet
594 } // namespace autofill
595