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> 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 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 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 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 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 123 WalletItems::MaskedInstrument::~MaskedInstrument() {} 124 125 scoped_ptr<WalletItems::MaskedInstrument> 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 192 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 221 bool WalletItems::MaskedInstrument::operator!=( 222 const WalletItems::MaskedInstrument& other) const { 223 return !(*this == other); 224 } 225 226 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 239 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 246 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 270 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 277 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 286 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 295 base::string16 WalletItems::MaskedInstrument::TypeAndLastFourDigits() const { 296 // TODO(dbeam): i18n. 297 return DisplayStringFromType(type_) + base::ASCIIToUTF16(" - ") + 298 last_four_digits(); 299 } 300 301 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 331 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 360 WalletItems::LegalDocument::~LegalDocument() {} 361 362 scoped_ptr<WalletItems::LegalDocument> 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> 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 387 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 393 bool WalletItems::LegalDocument::operator!=(const LegalDocument& other) const { 394 return !(*this == other); 395 } 396 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 403 WalletItems::LegalDocument::LegalDocument(const GURL& url, 404 const base::string16& display_name) 405 : url_(url), 406 display_name_(display_name) {} 407 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 420 WalletItems::~WalletItems() {} 421 422 scoped_ptr<WalletItems> 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 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 573 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 591 bool WalletItems::operator!=(const WalletItems& other) const { 592 return !(*this == other); 593 } 594 595 } // namespace wallet 596 } // namespace autofill 597