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