wallet_items.cc revision 68043e1e95eeb07d5cae7aca370b26518b0867d6
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
260base::string16 WalletItems::MaskedInstrument::DisplayName() const {
261#if defined(OS_ANDROID)
262  // TODO(aruslan): improve this stub implementation.
263  return descriptive_name();
264#else
265  return descriptive_name();
266#endif
267}
268
269base::string16 WalletItems::MaskedInstrument::DisplayNameDetail() const {
270#if defined(OS_ANDROID)
271  // TODO(aruslan): improve this stub implementation.
272  return address().DisplayName();
273#else
274  return base::string16();
275#endif
276}
277
278base::string16 WalletItems::MaskedInstrument::TypeAndLastFourDigits() const {
279  // TODO(dbeam): i18n.
280  return DisplayStringFromType(type_) + ASCIIToUTF16(" - ") +
281         last_four_digits();
282}
283
284const gfx::Image& WalletItems::MaskedInstrument::CardIcon() const {
285  int idr = 0;
286  switch (type_) {
287    case AMEX:
288      idr = IDR_AUTOFILL_CC_AMEX;
289      break;
290
291    case DISCOVER:
292      idr = IDR_AUTOFILL_CC_DISCOVER;
293      break;
294
295    case MASTER_CARD:
296      idr = IDR_AUTOFILL_CC_MASTERCARD;
297      break;
298
299    case VISA:
300      idr = IDR_AUTOFILL_CC_VISA;
301      break;
302
303    case SOLO:
304    case MAESTRO:
305    case SWITCH:
306    case UNKNOWN:
307      idr = IDR_AUTOFILL_CC_GENERIC;
308      break;
309  }
310
311  return ResourceBundle::GetSharedInstance().GetImageNamed(idr);
312}
313
314base::string16 WalletItems::MaskedInstrument::GetInfo(
315    const AutofillType& type,
316    const std::string& app_locale) const {
317  if (type.group() != CREDIT_CARD)
318    return address().GetInfo(type, app_locale);
319
320  switch (type.GetStorableType()) {
321    case CREDIT_CARD_NAME:
322      return address().recipient_name();
323
324    case CREDIT_CARD_NUMBER:
325      return DisplayName();
326
327    case CREDIT_CARD_EXP_4_DIGIT_YEAR:
328      return base::IntToString16(expiration_year());
329
330    case CREDIT_CARD_VERIFICATION_CODE:
331      break;
332
333    case CREDIT_CARD_TYPE:
334      return DisplayStringFromType(type_);
335
336    default:
337      NOTREACHED();
338  }
339
340  return base::string16();
341}
342
343WalletItems::LegalDocument::~LegalDocument() {}
344
345scoped_ptr<WalletItems::LegalDocument>
346    WalletItems::LegalDocument::CreateLegalDocument(
347    const base::DictionaryValue& dictionary) {
348  std::string id;
349  if (!dictionary.GetString("legal_document_id", &id)) {
350    DLOG(ERROR) << "Response from Google Wallet missing legal document id";
351    return scoped_ptr<LegalDocument>();
352  }
353
354  base::string16 display_name;
355  if (!dictionary.GetString("display_name", &display_name)) {
356    DLOG(ERROR) << "Response from Google Wallet missing display name";
357    return scoped_ptr<LegalDocument>();
358  }
359
360  return scoped_ptr<LegalDocument>(new LegalDocument(id, display_name));
361}
362
363scoped_ptr<WalletItems::LegalDocument>
364    WalletItems::LegalDocument::CreatePrivacyPolicyDocument() {
365  return scoped_ptr<LegalDocument>(new LegalDocument(
366      GURL(kPrivacyNoticeUrl),
367      l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_PRIVACY_POLICY_LINK)));
368}
369
370bool WalletItems::LegalDocument::operator==(const LegalDocument& other) const {
371  return id_ == other.id_ &&
372         url_ == other.url_ &&
373         display_name_ == other.display_name_;
374}
375
376bool WalletItems::LegalDocument::operator!=(const LegalDocument& other) const {
377  return !(*this == other);
378}
379
380WalletItems::LegalDocument::LegalDocument(const std::string& id,
381                                          const base::string16& display_name)
382    : id_(id),
383      url_(kLegalDocumentUrl + id),
384      display_name_(display_name) {}
385
386WalletItems::LegalDocument::LegalDocument(const GURL& url,
387                                          const base::string16& display_name)
388    : url_(url),
389      display_name_(display_name) {}
390
391WalletItems::WalletItems(const std::vector<RequiredAction>& required_actions,
392                         const std::string& google_transaction_id,
393                         const std::string& default_instrument_id,
394                         const std::string& default_address_id,
395                         const std::string& obfuscated_gaia_id,
396                         AmexPermission amex_permission)
397    : required_actions_(required_actions),
398      google_transaction_id_(google_transaction_id),
399      default_instrument_id_(default_instrument_id),
400      default_address_id_(default_address_id),
401      obfuscated_gaia_id_(obfuscated_gaia_id),
402      amex_permission_(amex_permission) {}
403
404WalletItems::~WalletItems() {}
405
406scoped_ptr<WalletItems>
407    WalletItems::CreateWalletItems(const base::DictionaryValue& dictionary) {
408  std::vector<RequiredAction> required_action;
409  const ListValue* required_action_list;
410  if (dictionary.GetList("required_action", &required_action_list)) {
411    for (size_t i = 0; i < required_action_list->GetSize(); ++i) {
412      std::string action_string;
413      if (required_action_list->GetString(i, &action_string)) {
414        RequiredAction action = ParseRequiredActionFromString(action_string);
415        if (!ActionAppliesToWalletItems(action)) {
416          DLOG(ERROR) << "Response from Google wallet with bad required action:"
417                         " \"" << action_string << "\"";
418          return scoped_ptr<WalletItems>();
419        }
420        required_action.push_back(action);
421      }
422    }
423  } else {
424    DVLOG(1) << "Response from Google wallet missing required actions";
425  }
426
427  std::string google_transaction_id;
428  if (!dictionary.GetString("google_transaction_id", &google_transaction_id) &&
429      required_action.empty()) {
430    DLOG(ERROR) << "Response from Google wallet missing google transaction id";
431    return scoped_ptr<WalletItems>();
432  }
433
434  std::string default_instrument_id;
435  if (!dictionary.GetString("default_instrument_id", &default_instrument_id))
436    DVLOG(1) << "Response from Google wallet missing default instrument id";
437
438  std::string default_address_id;
439  if (!dictionary.GetString("default_address_id", &default_address_id))
440    DVLOG(1) << "Response from Google wallet missing default_address_id";
441
442  std::string obfuscated_gaia_id;
443  if (!dictionary.GetString("obfuscated_gaia_id", &obfuscated_gaia_id))
444    DVLOG(1) << "Response from Google wallet missing obfuscated gaia id";
445
446  bool amex_disallowed = true;
447  if (!dictionary.GetBoolean("amex_disallowed", &amex_disallowed))
448    DVLOG(1) << "Response from Google wallet missing the amex_disallowed field";
449  AmexPermission amex_permission =
450      amex_disallowed ? AMEX_DISALLOWED : AMEX_ALLOWED;
451
452  scoped_ptr<WalletItems> wallet_items(new WalletItems(required_action,
453                                                       google_transaction_id,
454                                                       default_instrument_id,
455                                                       default_address_id,
456                                                       obfuscated_gaia_id,
457                                                       amex_permission));
458
459  const ListValue* legal_docs;
460  if (dictionary.GetList("required_legal_document", &legal_docs)) {
461    for (size_t i = 0; i < legal_docs->GetSize(); ++i) {
462      const DictionaryValue* legal_doc_dict;
463      if (legal_docs->GetDictionary(i, &legal_doc_dict)) {
464        scoped_ptr<LegalDocument> legal_doc(
465            LegalDocument::CreateLegalDocument(*legal_doc_dict));
466        if (legal_doc.get()) {
467          wallet_items->AddLegalDocument(legal_doc.Pass());
468        } else {
469          DLOG(ERROR) << "Malformed legal document in response from "
470                         "Google wallet";
471          return scoped_ptr<WalletItems>();
472        }
473      }
474    }
475
476    if (!legal_docs->empty()) {
477      // Always append the privacy policy link as well.
478      wallet_items->AddLegalDocument(
479          LegalDocument::CreatePrivacyPolicyDocument());
480    }
481  } else {
482    DVLOG(1) << "Response from Google wallet missing legal docs";
483  }
484
485  const ListValue* instruments;
486  if (dictionary.GetList("instrument", &instruments)) {
487    for (size_t i = 0; i < instruments->GetSize(); ++i) {
488      const DictionaryValue* instrument_dict;
489      if (instruments->GetDictionary(i, &instrument_dict)) {
490        scoped_ptr<MaskedInstrument> instrument(
491            MaskedInstrument::CreateMaskedInstrument(*instrument_dict));
492        if (instrument.get())
493          wallet_items->AddInstrument(instrument.Pass());
494        else
495          DLOG(ERROR) << "Malformed instrument in response from Google Wallet";
496      }
497    }
498  } else {
499    DVLOG(1) << "Response from Google wallet missing instruments";
500  }
501
502  const ListValue* addresses;
503  if (dictionary.GetList("address", &addresses)) {
504    for (size_t i = 0; i < addresses->GetSize(); ++i) {
505      const DictionaryValue* address_dict;
506      if (addresses->GetDictionary(i, &address_dict)) {
507        scoped_ptr<Address> address(
508            Address::CreateAddressWithID(*address_dict));
509        if (address.get())
510          wallet_items->AddAddress(address.Pass());
511        else
512          DLOG(ERROR) << "Malformed address in response from Google Wallet";
513      }
514    }
515  } else {
516    DVLOG(1) << "Response from Google wallet missing addresses";
517  }
518
519  return wallet_items.Pass();
520}
521
522bool WalletItems::operator==(const WalletItems& other) const {
523  return google_transaction_id_ == other.google_transaction_id_ &&
524         default_instrument_id_ == other.default_instrument_id_ &&
525         default_address_id_ == other.default_address_id_ &&
526         required_actions_ == other.required_actions_ &&
527         obfuscated_gaia_id_ == other.obfuscated_gaia_id_ &&
528         VectorsAreEqual<MaskedInstrument>(instruments(),
529                                           other.instruments()) &&
530         VectorsAreEqual<Address>(addresses(), other.addresses()) &&
531         VectorsAreEqual<LegalDocument>(legal_documents(),
532                                        other.legal_documents());
533}
534
535bool WalletItems::operator!=(const WalletItems& other) const {
536  return !(*this == other);
537}
538
539}  // namespace wallet
540}  // namespace autofill
541