wallet_items.cc revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
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
21namespace autofill {
22namespace wallet {
23
24namespace {
25
26const char kLegalDocumentUrl[] =
27    "https://wallet.google.com/legaldocument?docId=";
28const char kPrivacyNoticeUrl[] = "https://wallet.google.com/files/privacy.html";
29
30// TODO(estade): move to base/.
31template<class T>
32bool 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
44WalletItems::MaskedInstrument::Type
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
63WalletItems::MaskedInstrument::Status
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
84base::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
101WalletItems::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
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 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, &currency))
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
205bool 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
236bool WalletItems::MaskedInstrument::operator!=(
237    const WalletItems::MaskedInstrument& other) const {
238  return !(*this == other);
239}
240
241const 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
254bool 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
261bool 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
285std::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
292base::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
301base::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
310base::string16 WalletItems::MaskedInstrument::TypeAndLastFourDigits() const {
311  // TODO(dbeam): i18n.
312  return DisplayStringFromType(type_) + ASCIIToUTF16(" - ") +
313         last_four_digits();
314}
315
316const 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
346base::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
375WalletItems::LegalDocument::~LegalDocument() {}
376
377scoped_ptr<WalletItems::LegalDocument>
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
395scoped_ptr<WalletItems::LegalDocument>
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
402bool WalletItems::LegalDocument::operator==(const LegalDocument& other) const {
403  return id_ == other.id_ &&
404         url_ == other.url_ &&
405         display_name_ == other.display_name_;
406}
407
408bool WalletItems::LegalDocument::operator!=(const LegalDocument& other) const {
409  return !(*this == other);
410}
411
412WalletItems::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
418WalletItems::LegalDocument::LegalDocument(const GURL& url,
419                                          const base::string16& display_name)
420    : url_(url),
421      display_name_(display_name) {}
422
423WalletItems::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
435WalletItems::~WalletItems() {}
436
437scoped_ptr<WalletItems>
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
560void 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
572bool 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
589bool WalletItems::operator!=(const WalletItems& other) const {
590  return !(*this == other);
591}
592
593}  // namespace wallet
594}  // namespace autofill
595