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