wallet_items.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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
23namespace autofill {
24namespace wallet {
25
26namespace {
27
28const char kLegalDocumentUrl[] =
29    "https://wallet.google.com/legaldocument?docId=";
30const char kPrivacyNoticeUrl[] = "https://wallet.google.com/files/privacy.html";
31
32// TODO(estade): move to base/.
33template<class T>
34bool 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
46WalletItems::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
65WalletItems::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
86base::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
103WalletItems::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
123WalletItems::MaskedInstrument::~MaskedInstrument() {}
124
125scoped_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
192bool 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
221bool WalletItems::MaskedInstrument::operator!=(
222    const WalletItems::MaskedInstrument& other) const {
223  return !(*this == other);
224}
225
226const 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
239bool 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
246bool 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
270std::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
277base::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
286base::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
295base::string16 WalletItems::MaskedInstrument::TypeAndLastFourDigits() const {
296  // TODO(dbeam): i18n.
297  return DisplayStringFromType(type_) + base::ASCIIToUTF16(" - ") +
298         last_four_digits();
299}
300
301const 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
331base::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
360WalletItems::LegalDocument::~LegalDocument() {}
361
362scoped_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
380scoped_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
387bool WalletItems::LegalDocument::operator==(const LegalDocument& other) const {
388  return id_ == other.id_ &&
389         url_ == other.url_ &&
390         display_name_ == other.display_name_;
391}
392
393bool WalletItems::LegalDocument::operator!=(const LegalDocument& other) const {
394  return !(*this == other);
395}
396
397WalletItems::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
403WalletItems::LegalDocument::LegalDocument(const GURL& url,
404                                          const base::string16& display_name)
405    : url_(url),
406      display_name_(display_name) {}
407
408WalletItems::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
420WalletItems::~WalletItems() {}
421
422scoped_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
561void 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
573bool 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
591bool WalletItems::operator!=(const WalletItems& other) const {
592  return !(*this == other);
593}
594
595}  // namespace wallet
596}  // namespace autofill
597