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