summaryrefslogtreecommitdiffstats
path: root/chrome/browser/autofill
diff options
context:
space:
mode:
authorahutter@chromium.org <ahutter@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-12-20 02:46:28 +0000
committerahutter@chromium.org <ahutter@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-12-20 02:46:28 +0000
commitce63d6b29059447b36aa2fc52ea608b1e785692e (patch)
treedb5a93bac2703504c6c8564c6ef58ec02abd8353 /chrome/browser/autofill
parent276c8b6cc32fe5e03ad779ff2c506a090b87cd4f (diff)
downloadchromium_src-ce63d6b29059447b36aa2fc52ea608b1e785692e.zip
chromium_src-ce63d6b29059447b36aa2fc52ea608b1e785692e.tar.gz
chromium_src-ce63d6b29059447b36aa2fc52ea608b1e785692e.tar.bz2
Integrating Online Wallet into Chrome. This CL is modeled after the Gaia OAuth client.
BUG=163609 Review URL: https://chromiumcodereview.appspot.com/11293078 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@174078 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/autofill')
-rw-r--r--chrome/browser/autofill/wallet/cart.cc24
-rw-r--r--chrome/browser/autofill/wallet/cart.h45
-rw-r--r--chrome/browser/autofill/wallet/cart_unittest.cc20
-rw-r--r--chrome/browser/autofill/wallet/full_wallet.cc208
-rw-r--r--chrome/browser/autofill/wallet/full_wallet.h94
-rw-r--r--chrome/browser/autofill/wallet/full_wallet_unittest.cc420
-rw-r--r--chrome/browser/autofill/wallet/wallet_address.cc172
-rw-r--r--chrome/browser/autofill/wallet/wallet_address.h110
-rw-r--r--chrome/browser/autofill/wallet/wallet_address_unittest.cc231
-rw-r--r--chrome/browser/autofill/wallet/wallet_client.cc285
-rw-r--r--chrome/browser/autofill/wallet/wallet_client.h152
-rw-r--r--chrome/browser/autofill/wallet/wallet_client_unittest.cc396
-rw-r--r--chrome/browser/autofill/wallet/wallet_items.cc360
-rw-r--r--chrome/browser/autofill/wallet/wallet_items.h207
-rw-r--r--chrome/browser/autofill/wallet/wallet_items_unittest.cc447
-rw-r--r--chrome/browser/autofill/wallet/wallet_service_url.cc62
-rw-r--r--chrome/browser/autofill/wallet/wallet_service_url.h23
-rw-r--r--chrome/browser/autofill/wallet/wallet_service_url_unittest.cc32
18 files changed, 3288 insertions, 0 deletions
diff --git a/chrome/browser/autofill/wallet/cart.cc b/chrome/browser/autofill/wallet/cart.cc
new file mode 100644
index 0000000..25db991
--- /dev/null
+++ b/chrome/browser/autofill/wallet/cart.cc
@@ -0,0 +1,24 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/autofill/wallet/cart.h"
+
+#include "base/values.h"
+
+namespace wallet {
+
+Cart::Cart(const std::string& total_price, const std::string& currency_code)
+ : total_price_(total_price), currency_code_(currency_code) {}
+
+Cart::~Cart() {}
+
+scoped_ptr<base::DictionaryValue> Cart::ToDictionary() const {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("total_price", total_price_);
+ dict->SetString("currency_code", currency_code_);
+ return scoped_ptr<base::DictionaryValue>(dict);
+}
+
+} // namespace wallet
+
diff --git a/chrome/browser/autofill/wallet/cart.h b/chrome/browser/autofill/wallet/cart.h
new file mode 100644
index 0000000..834bc5c
--- /dev/null
+++ b/chrome/browser/autofill/wallet/cart.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_AUTOFILL_WALLET_CART_H_
+#define CHROME_BROWSER_AUTOFILL_WALLET_CART_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace wallet {
+
+// Container object for purchase data provided by the browser. The enclosed data
+// is required to request a FullWallet from Online Wallet in order to set
+// spending limits on the generated proxy card. If the actual amount is not
+// available, the maximum allowable value should be used: $1850 USD. Online
+// Wallet is designed to accept price information in addition to information
+// about the items being purchased hence the name Cart.
+class Cart {
+ public:
+ Cart(const std::string& total_price, const std::string& currency_code);
+ ~Cart();
+ const std::string& total_price() const { return total_price_; }
+ const std::string& currency_code() const { return currency_code_; }
+ scoped_ptr<base::DictionaryValue> ToDictionary() const;
+
+ private:
+ // |total_price_| must be a formatted as a double with no more than two
+ // decimals, e.g. 100.99
+ std::string total_price_;
+ // |currency_code_| must be one of the ISO 4217 currency codes, e.g. USD.
+ std::string currency_code_;
+ DISALLOW_COPY_AND_ASSIGN(Cart);
+};
+
+} // namespace wallet
+
+#endif // CHROME_BROWSER_AUTOFILL_WALLET_CART_H_
+
diff --git a/chrome/browser/autofill/wallet/cart_unittest.cc b/chrome/browser/autofill/wallet/cart_unittest.cc
new file mode 100644
index 0000000..a094913
--- /dev/null
+++ b/chrome/browser/autofill/wallet/cart_unittest.cc
@@ -0,0 +1,20 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/values.h"
+#include "chrome/browser/autofill/wallet/cart.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace wallet {
+
+TEST(Cart, ToDictionary) {
+ base::DictionaryValue expected;
+ expected.SetString("total_price", "total_price");
+ expected.SetString("currency_code", "currency_code");
+ Cart cart("total_price", "currency_code");
+ ASSERT_TRUE(expected.Equals(cart.ToDictionary().get()));
+}
+
+} // namespace wallet
+
diff --git a/chrome/browser/autofill/wallet/full_wallet.cc b/chrome/browser/autofill/wallet/full_wallet.cc
new file mode 100644
index 0000000..a163cff
--- /dev/null
+++ b/chrome/browser/autofill/wallet/full_wallet.cc
@@ -0,0 +1,208 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/autofill/wallet/full_wallet.h"
+
+#include "base/logging.h"
+#include "base/string_number_conversions.h"
+#include "base/values.h"
+
+namespace {
+
+const size_t kPanSize = 16;
+const size_t kBinSize = 6;
+const size_t kCvnSize = 3;
+
+} // anonymous namespace
+
+namespace wallet {
+
+FullWallet::FullWallet(int expiration_month,
+ int expiration_year,
+ const std::string& iin,
+ const std::string& encrypted_rest,
+ scoped_ptr<Address> billing_address,
+ scoped_ptr<Address> shipping_address,
+ const std::vector<std::string>& required_actions)
+ : expiration_month_(expiration_month),
+ expiration_year_(expiration_year),
+ iin_(iin),
+ encrypted_rest_(encrypted_rest),
+ billing_address_(billing_address.Pass()),
+ shipping_address_(shipping_address.Pass()),
+ required_actions_(required_actions) {
+ DCHECK(required_actions_.size() > 0 || billing_address_.get());
+}
+
+FullWallet::~FullWallet() {}
+
+scoped_ptr<FullWallet>
+ FullWallet::CreateFullWallet(const DictionaryValue& dictionary) {
+ const ListValue* required_actions_list;
+ std::vector<std::string> required_actions;
+ if (dictionary.GetList("required_action", &required_actions_list)) {
+ for (size_t i = 0; i < required_actions_list->GetSize(); ++i) {
+ std::string action;
+ if (required_actions_list->GetString(i, &action))
+ required_actions.push_back(action);
+ }
+ if (required_actions.size() > 0)
+ return scoped_ptr<FullWallet>(new FullWallet(-1,
+ -1,
+ "",
+ "",
+ scoped_ptr<Address>(),
+ scoped_ptr<Address>(),
+ required_actions));
+ } else {
+ DVLOG(1) << "Response from Google wallet missing required actions";
+ }
+
+ int expiration_month;
+ if (!dictionary.GetInteger("expiration_month", &expiration_month)) {
+ DLOG(ERROR) << "Response from Google wallet missing expiration month";
+ return scoped_ptr<FullWallet>();
+ }
+
+ int expiration_year;
+ if (!dictionary.GetInteger("expiration_year", &expiration_year)) {
+ DLOG(ERROR) << "Response from Google wallet missing expiration year";
+ return scoped_ptr<FullWallet>();
+ }
+
+ std::string iin;
+ if (!dictionary.GetString("iin", &iin)) {
+ DLOG(ERROR) << "Response from Google wallet missing iin";
+ return scoped_ptr<FullWallet>();
+ }
+
+ std::string encrypted_rest;
+ if (!dictionary.GetString("rest", &encrypted_rest)) {
+ DLOG(ERROR) << "Response from Google wallet missing rest";
+ return scoped_ptr<FullWallet>();
+ }
+
+ const DictionaryValue* billing_address_dict;
+ if (!dictionary.GetDictionary("billing_address", &billing_address_dict)) {
+ DLOG(ERROR) << "Response from Google wallet missing billing address";
+ return scoped_ptr<FullWallet>();
+ }
+
+ scoped_ptr<Address> billing_address =
+ Address::CreateAddressWithID(*billing_address_dict);
+ if (!billing_address.get()) {
+ DLOG(ERROR) << "Response from Google wallet has malformed billing address";
+ return scoped_ptr<FullWallet>();
+ }
+
+ const DictionaryValue* shipping_address_dict;
+ scoped_ptr<Address> shipping_address;
+ if (dictionary.GetDictionary("shipping_address", &shipping_address_dict)) {
+ shipping_address =
+ Address::CreateAddressWithID(*shipping_address_dict);
+ } else {
+ DVLOG(1) << "Response from Google wallet missing shipping address";
+ }
+
+ return scoped_ptr<FullWallet>(new FullWallet(expiration_month,
+ expiration_year,
+ iin,
+ encrypted_rest,
+ billing_address.Pass(),
+ shipping_address.Pass(),
+ required_actions));
+}
+
+bool FullWallet::operator==(const FullWallet& other) const {
+ if (expiration_month_ != other.expiration_month_)
+ return false;
+
+ if (expiration_year_ != other.expiration_year_)
+ return false;
+
+ if (iin_ != other.iin_)
+ return false;
+
+ if (encrypted_rest_ != other.encrypted_rest_)
+ return false;
+
+ if (billing_address_.get() && other.billing_address_.get()) {
+ if (*billing_address_.get() != *other.billing_address_.get())
+ return false;
+ } else if (billing_address_.get() || other.billing_address_.get()) {
+ return false;
+ }
+
+ if (shipping_address_.get() && other.shipping_address_.get()) {
+ if (*shipping_address_.get() != *other.shipping_address_.get())
+ return false;
+ } else if (shipping_address_.get() || other.shipping_address_.get()) {
+ return false;
+ }
+
+ if (required_actions_ != other.required_actions_)
+ return false;
+
+ return true;
+}
+
+bool FullWallet::operator!=(const FullWallet& other) const {
+ return !(*this == other);
+}
+
+const std::string& FullWallet::GetPan(void* otp, size_t length) {
+ if (cvn_.empty())
+ DecryptCardInfo(reinterpret_cast<uint8*>(otp), length);
+ return pan_;
+}
+
+const std::string& FullWallet::GetCvn(void* otp, size_t length) {
+ if (pan_.empty())
+ DecryptCardInfo(reinterpret_cast<uint8*>(otp), length);
+ return cvn_;
+}
+
+void FullWallet::DecryptCardInfo(uint8* otp, size_t length) {
+ std::vector<uint8> operating_data;
+ // Convert |encrypted_rest_| to bytes so we can decrypt it with |otp|.
+ if (!base::HexStringToBytes(encrypted_rest_, &operating_data)) {
+ DLOG(ERROR) << "Failed to parse encrypted rest";
+ return;
+ }
+
+ // Ensure |otp| and |encrypted_rest_| are of the same length otherwise
+ // something has gone wrong and we can't decrypt the data.
+ DCHECK_EQ(length, operating_data.size());
+
+ scoped_array<uint8> result(new uint8[length]);
+ // XOR |otp| with the encrypted data to decrypt.
+ for (size_t i = 0; i < length; ++i)
+ result.get()[i] = otp[i] ^ operating_data[i];
+
+ // There is no uint8* to int64 so convert the decrypted data to hex and then
+ // parse the hex to an int64 before getting the int64 as a string.
+ std::string hex_decrypted = base::HexEncode(result.get(), length);
+
+ int64 decrypted;
+ if (!base::HexStringToInt64(hex_decrypted, &decrypted)) {
+ DLOG(ERROR) << "Failed to parse decrypted data in hex to int64";
+ return;
+ }
+ std::string card_info = base::Int64ToString(decrypted);
+
+ size_t padded_length = kPanSize - kBinSize + kCvnSize;
+ // |card_info| is PAN without the IIN concatenated with the CVN, i.e.
+ // PANPANPANPCVN. If what was decrypted is not of that size the front needs
+ // to be padded with 0's until it is.
+ if (card_info.size() != padded_length)
+ card_info.insert(card_info.begin(), padded_length - card_info.size(), '0');
+
+ // Separate out the PAN from the CVN.
+ size_t split = kPanSize - kBinSize;
+ cvn_ = card_info.substr(split);
+ pan_ = iin_ + card_info.substr(0, split);
+}
+
+} // namespace wallet
+
diff --git a/chrome/browser/autofill/wallet/full_wallet.h b/chrome/browser/autofill/wallet/full_wallet.h
new file mode 100644
index 0000000..848c2e1
--- /dev/null
+++ b/chrome/browser/autofill/wallet/full_wallet.h
@@ -0,0 +1,94 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_AUTOFILL_WALLET_FULL_WALLET_H_
+#define CHROME_BROWSER_AUTOFILL_WALLET_FULL_WALLET_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "chrome/browser/autofill/wallet/wallet_address.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace wallet {
+
+class FullWalletTest;
+
+// FullWallet contains all the information a merchant requires from a user for
+// that user to make a purchase.
+class FullWallet {
+ public:
+ ~FullWallet();
+
+ // Returns an empty scoped_ptr if the input invalid, an empty wallet with
+ // required actions if there are any, or a valid wallet.
+ static scoped_ptr<FullWallet>
+ CreateFullWallet(const base::DictionaryValue& dictionary);
+
+ // Decrypts and returns primary account number (PAN) using generated one time
+ // pad, |otp|.
+ const std::string& GetPan(void* otp, size_t length);
+
+ // Decrypts and returns card verification number (CVN) using generated one
+ // time pad, |otp|.
+ const std::string& GetCvn(void* otp, size_t length);
+
+ bool operator==(const FullWallet& other) const;
+ bool operator!=(const FullWallet& other) const;
+
+ // If there are required actions |billing_address_| might contain NULL.
+ const Address* billing_address() const { return billing_address_.get(); }
+
+ // If there are required actions or shipping address is not required
+ // |shipping_address_| might contain NULL.
+ const Address* shipping_address() const { return shipping_address_.get(); }
+
+ const std::vector<std::string>& required_actions() const {
+ return required_actions_;
+ }
+ int expiration_month() const { return expiration_month_; }
+ int expiration_year() const { return expiration_year_; }
+
+ private:
+ friend class FullWalletTest;
+ FRIEND_TEST_ALL_PREFIXES(FullWalletTest, CreateFullWallet);
+ FRIEND_TEST_ALL_PREFIXES(FullWalletTest, CreateFullWalletWithRequiredActions);
+ FullWallet(int expiration_month,
+ int expiration_year,
+ const std::string& iin,
+ const std::string& encrypted_rest,
+ scoped_ptr<Address> billing_address,
+ scoped_ptr<Address> shipping_address,
+ const std::vector<std::string>& required_actions);
+ void DecryptCardInfo(uint8* otp, size_t length);
+ int expiration_month_;
+ int expiration_year_;
+ // Primary account number (PAN). Its format is \d{16}.
+ std::string pan_;
+ // Card verification number (CVN). Its format is \d{3}.
+ std::string cvn_;
+ // Issuer identification number (IIN). Its format is \d{6}.
+ std::string iin_;
+ // Encrypted concatentation of CVN and PAN without IIN
+ std::string encrypted_rest_;
+ scoped_ptr<Address> billing_address_;
+ scoped_ptr<Address> shipping_address_;
+ // Actions that must be completed by the user before a FullWallet can be
+ // issued to them by the Online Wallet service.
+ // TODO(ahutter): |required_actions_| should be members of an enum not
+ // strings. See http://crbug.com/165195.
+ std::vector<std::string> required_actions_;
+ DISALLOW_COPY_AND_ASSIGN(FullWallet);
+};
+
+} // namespace wallet
+
+#endif // CHROME_BROWSER_AUTOFILL_WALLET_FULL_WALLET_H_
+
diff --git a/chrome/browser/autofill/wallet/full_wallet_unittest.cc b/chrome/browser/autofill/wallet/full_wallet_unittest.cc
new file mode 100644
index 0000000..50d9354
--- /dev/null
+++ b/chrome/browser/autofill/wallet/full_wallet_unittest.cc
@@ -0,0 +1,420 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "chrome/browser/autofill/wallet/full_wallet.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char kFullWalletValidResponse[] =
+ "{"
+ " \"expiration_month\":12,"
+ " \"expiration_year\":2012,"
+ " \"iin\":\"iin\","
+ " \"rest\":\"rest\","
+ " \"billing_address\":"
+ " {"
+ " \"id\":\"id\","
+ " \"phone_number\":\"phone_number\","
+ " \"postal_address\":"
+ " {"
+ " \"recipient_name\":\"recipient_name\","
+ " \"address_line\":"
+ " ["
+ " \"address_line_1\","
+ " \"address_line_2\""
+ " ],"
+ " \"locality_name\":\"locality_name\","
+ " \"administrative_area_name\":\"administrative_area_name\","
+ " \"postal_code_number\":\"postal_code_number\","
+ " \"country_name_code\":\"country_name_code\""
+ " }"
+ " },"
+ " \"shipping_address\":"
+ " {"
+ " \"id\":\"ship_id\","
+ " \"phone_number\":\"ship_phone_number\","
+ " \"postal_address\":"
+ " {"
+ " \"recipient_name\":\"ship_recipient_name\","
+ " \"address_line\":"
+ " ["
+ " \"ship_address_line_1\","
+ " \"ship_address_line_2\""
+ " ],"
+ " \"locality_name\":\"ship_locality_name\","
+ " \"administrative_area_name\":\"ship_admin_area_name\","
+ " \"postal_code_number\":\"ship_postal_code_number\","
+ " \"country_name_code\":\"ship_country_name_code\""
+ " }"
+ " },"
+ " \"required_action\":"
+ " ["
+ " ]"
+ "}";
+
+const char kFullWalletMissingExpirationMonth[] =
+ "{"
+ " \"expiration_year\":2012,"
+ " \"iin\":\"iin\","
+ " \"rest\":\"rest\","
+ " \"billing_address\":"
+ " {"
+ " \"id\":\"id\","
+ " \"phone_number\":\"phone_number\","
+ " \"postal_address\":"
+ " {"
+ " \"recipient_name\":\"recipient_name\","
+ " \"address_line\":"
+ " ["
+ " \"address_line_1\","
+ " \"address_line_2\""
+ " ],"
+ " \"locality_name\":\"locality_name\","
+ " \"administrative_area_name\":\"administrative_area_name\","
+ " \"postal_code_number\":\"postal_code_number\","
+ " \"country_name_code\":\"country_name_code\""
+ " }"
+ " },"
+ " \"shipping_address\":"
+ " {"
+ " \"id\":\"ship_id\","
+ " \"phone_number\":\"ship_phone_number\","
+ " \"postal_address\":"
+ " {"
+ " \"recipient_name\":\"ship_recipient_name\","
+ " \"address_line\":"
+ " ["
+ " \"ship_address_line_1\","
+ " \"ship_address_line_2\""
+ " ],"
+ " \"locality_name\":\"ship_locality_name\","
+ " \"administrative_area_name\":\"ship_admin_area_name\","
+ " \"postal_code_number\":\"ship_postal_code_number\","
+ " \"country_name_code\":\"ship_country_name_code\""
+ " }"
+ " },"
+ " \"required_action\":"
+ " ["
+ " ]"
+ "}";
+
+const char kFullWalletMissingExpirationYear[] =
+ "{"
+ " \"expiration_month\":12,"
+ " \"iin\":\"iin\","
+ " \"rest\":\"rest\","
+ " \"billing_address\":"
+ " {"
+ " \"id\":\"id\","
+ " \"phone_number\":\"phone_number\","
+ " \"postal_address\":"
+ " {"
+ " \"recipient_name\":\"recipient_name\","
+ " \"address_line\":"
+ " ["
+ " \"address_line_1\","
+ " \"address_line_2\""
+ " ],"
+ " \"locality_name\":\"locality_name\","
+ " \"administrative_area_name\":\"administrative_area_name\","
+ " \"postal_code_number\":\"postal_code_number\","
+ " \"country_name_code\":\"country_name_code\""
+ " }"
+ " },"
+ " \"shipping_address\":"
+ " {"
+ " \"id\":\"ship_id\","
+ " \"phone_number\":\"ship_phone_number\","
+ " \"postal_address\":"
+ " {"
+ " \"recipient_name\":\"ship_recipient_name\","
+ " \"address_line\":"
+ " ["
+ " \"ship_address_line_1\","
+ " \"ship_address_line_2\""
+ " ],"
+ " \"locality_name\":\"ship_locality_name\","
+ " \"administrative_area_name\":\"ship_admin_area_name\","
+ " \"postal_code_number\":\"ship_postal_code_number\","
+ " \"country_name_code\":\"ship_country_name_code\""
+ " }"
+ " },"
+ " \"required_action\":"
+ " ["
+ " ]"
+ "}";
+
+const char kFullWalletMissingIin[] =
+ "{"
+ " \"expiration_month\":12,"
+ " \"expiration_year\":2012,"
+ " \"rest\":\"rest\","
+ " \"billing_address\":"
+ " {"
+ " \"id\":\"id\","
+ " \"phone_number\":\"phone_number\","
+ " \"postal_address\":"
+ " {"
+ " \"recipient_name\":\"recipient_name\","
+ " \"address_line\":"
+ " ["
+ " \"address_line_1\","
+ " \"address_line_2\""
+ " ],"
+ " \"locality_name\":\"locality_name\","
+ " \"administrative_area_name\":\"administrative_area_name\","
+ " \"postal_code_number\":\"postal_code_number\","
+ " \"country_name_code\":\"country_name_code\""
+ " }"
+ " },"
+ " \"shipping_address\":"
+ " {"
+ " \"id\":\"ship_id\","
+ " \"phone_number\":\"ship_phone_number\","
+ " \"postal_address\":"
+ " {"
+ " \"recipient_name\":\"ship_recipient_name\","
+ " \"address_line\":"
+ " ["
+ " \"ship_address_line_1\","
+ " \"ship_address_line_2\""
+ " ],"
+ " \"locality_name\":\"ship_locality_name\","
+ " \"administrative_area_name\":\"ship_admin_area_name\","
+ " \"postal_code_number\":\"ship_postal_code_number\","
+ " \"country_name_code\":\"ship_country_name_code\""
+ " }"
+ " },"
+ " \"required_action\":"
+ " ["
+ " ]"
+ "}";
+
+const char kFullWalletMissingRest[] =
+ "{"
+ " \"expiration_month\":12,"
+ " \"expiration_year\":2012,"
+ " \"iin\":\"iin\","
+ " \"billing_address\":"
+ " {"
+ " \"id\":\"id\","
+ " \"phone_number\":\"phone_number\","
+ " \"postal_address\":"
+ " {"
+ " \"recipient_name\":\"recipient_name\","
+ " \"address_line\":"
+ " ["
+ " \"address_line_1\","
+ " \"address_line_2\""
+ " ],"
+ " \"locality_name\":\"locality_name\","
+ " \"administrative_area_name\":\"administrative_area_name\","
+ " \"postal_code_number\":\"postal_code_number\","
+ " \"country_name_code\":\"country_name_code\""
+ " }"
+ " },"
+ " \"shipping_address\":"
+ " {"
+ " \"id\":\"ship_id\","
+ " \"phone_number\":\"ship_phone_number\","
+ " \"postal_address\":"
+ " {"
+ " \"recipient_name\":\"ship_recipient_name\","
+ " \"address_line\":"
+ " ["
+ " \"ship_address_line_1\","
+ " \"ship_address_line_2\""
+ " ],"
+ " \"locality_name\":\"ship_locality_name\","
+ " \"administrative_area_name\":\"ship_admin_area_name\","
+ " \"postal_code_number\":\"ship_postal_code_number\","
+ " \"country_name_code\":\"ship_country_name_code\""
+ " }"
+ " },"
+ " \"required_action\":"
+ " ["
+ " ]"
+ "}";
+
+const char kFullWalletMissingBillingAddress[] =
+ "{"
+ " \"expiration_month\":12,"
+ " \"expiration_year\":2012,"
+ " \"iin\":\"iin\","
+ " \"rest\":\"rest\","
+ " \"shipping_address\":"
+ " {"
+ " \"id\":\"ship_id\","
+ " \"phone_number\":\"ship_phone_number\","
+ " \"postal_address\":"
+ " {"
+ " \"recipient_name\":\"ship_recipient_name\","
+ " \"address_line\":"
+ " ["
+ " \"ship_address_line_1\","
+ " \"ship_address_line_2\""
+ " ],"
+ " \"locality_name\":\"ship_locality_name\","
+ " \"administrative_area_name\":\"ship_admin_area_name\","
+ " \"postal_code_number\":\"ship_postal_code_number\","
+ " \"country_name_code\":\"ship_country_name_code\""
+ " }"
+ " },"
+ " \"required_action\":"
+ " ["
+ " ]"
+ "}";
+
+const char kFullWalletWithRequiredActions[] =
+ "{"
+ " \"required_action\":"
+ " ["
+ " \"required_action\""
+ " ]"
+ "}";
+
+const char kFullWalletMalformedBillingAddress[] =
+ "{"
+ " \"expiration_month\":12,"
+ " \"expiration_year\":2012,"
+ " \"iin\":\"iin\","
+ " \"rest\":\"rest\","
+ " \"billing_address\":"
+ " {"
+ " \"id\":\"id\","
+ " \"phone_number\":\"phone_number\","
+ " \"postal_address\":"
+ " {"
+ " \"recipient_name\":\"recipient_name\","
+ " \"address_line\":"
+ " ["
+ " \"address_line_1\","
+ " \"address_line_2\""
+ " ],"
+ " \"locality_name\":\"locality_name\","
+ " \"administrative_area_name\":\"administrative_area_name\""
+ " }"
+ " },"
+ " \"shipping_address\":"
+ " {"
+ " \"id\":\"ship_id\","
+ " \"phone_number\":\"ship_phone_number\","
+ " \"postal_address\":"
+ " {"
+ " \"recipient_name\":\"ship_recipient_name\","
+ " \"address_line\":"
+ " ["
+ " \"ship_address_line_1\","
+ " \"ship_address_line_2\""
+ " ],"
+ " \"locality_name\":\"ship_locality_name\","
+ " \"administrative_area_name\":\"ship_admin_area_name\","
+ " \"postal_code_number\":\"ship_postal_code_number\","
+ " \"country_name_code\":\"ship_country_name_code\""
+ " }"
+ " },"
+ " \"required_action\":"
+ " ["
+ " ]"
+ "}";
+
+} // anonymous namespace
+
+namespace wallet {
+
+class FullWalletTest : public testing::Test {
+ public:
+ FullWalletTest() {}
+ protected:
+ void SetUpDictionary(const std::string& json) {
+ scoped_ptr<Value> value(base::JSONReader::Read(json));
+ DCHECK(value.get());
+ DCHECK(value->IsType(Value::TYPE_DICTIONARY));
+ dict.reset(static_cast<DictionaryValue*>(value.release()));
+ }
+ scoped_ptr<DictionaryValue> dict;
+};
+
+TEST_F(FullWalletTest, CreateFullWalletMissingExpirationMonth) {
+ SetUpDictionary(kFullWalletMissingExpirationMonth);
+ ASSERT_EQ(NULL, FullWallet::CreateFullWallet(*dict).get());
+}
+
+TEST_F(FullWalletTest, CreateFullWalletMissingExpirationYear) {
+ SetUpDictionary(kFullWalletMissingExpirationYear);
+ ASSERT_EQ(NULL, FullWallet::CreateFullWallet(*dict).get());
+}
+
+TEST_F(FullWalletTest, CreateFullWalletMissingIin) {
+ SetUpDictionary(kFullWalletMissingIin);
+ ASSERT_EQ(NULL, FullWallet::CreateFullWallet(*dict).get());
+}
+
+TEST_F(FullWalletTest, CreateFullWalletMissingRest) {
+ SetUpDictionary(kFullWalletMissingRest);
+ ASSERT_EQ(NULL, FullWallet::CreateFullWallet(*dict).get());
+}
+
+TEST_F(FullWalletTest, CreateFullWalletMissingBillingAddress) {
+ SetUpDictionary(kFullWalletMissingBillingAddress);
+ ASSERT_EQ(NULL, FullWallet::CreateFullWallet(*dict).get());
+}
+
+TEST_F(FullWalletTest, CreateFullWalletMalformedBillingAddress) {
+ SetUpDictionary(kFullWalletMalformedBillingAddress);
+ ASSERT_EQ(NULL, FullWallet::CreateFullWallet(*dict).get());
+}
+
+TEST_F(FullWalletTest, CreateFullWalletWithRequiredActions) {
+ SetUpDictionary(kFullWalletWithRequiredActions);
+ std::vector<std::string> required_actions;
+ required_actions.push_back("required_action");
+ FullWallet full_wallet(-1,
+ -1,
+ "",
+ "",
+ scoped_ptr<Address>(),
+ scoped_ptr<Address>(),
+ required_actions);
+ ASSERT_EQ(full_wallet, *FullWallet::CreateFullWallet(*dict));
+}
+
+TEST_F(FullWalletTest, CreateFullWallet) {
+ SetUpDictionary(kFullWalletValidResponse);
+ scoped_ptr<Address> billing_address(new Address("country_name_code",
+ "recipient_name",
+ "address_line_1",
+ "address_line_2",
+ "locality_name",
+ "administrative_area_name",
+ "postal_code_number",
+ "phone_number",
+ "id"));
+ scoped_ptr<Address> shipping_address(new Address("ship_country_name_code",
+ "ship_recipient_name",
+ "ship_address_line_1",
+ "ship_address_line_2",
+ "ship_locality_name",
+ "ship_admin_area_name",
+ "ship_postal_code_number",
+ "ship_phone_number",
+ "ship_id"));
+ std::vector<std::string> required_actions;
+ FullWallet full_wallet(12,
+ 2012,
+ "iin",
+ "rest",
+ billing_address.Pass(),
+ shipping_address.Pass(),
+ required_actions);
+ ASSERT_EQ(full_wallet, *FullWallet::CreateFullWallet(*dict));
+}
+
+} // namespace wallet
+
diff --git a/chrome/browser/autofill/wallet/wallet_address.cc b/chrome/browser/autofill/wallet/wallet_address.cc
new file mode 100644
index 0000000..59901ff
--- /dev/null
+++ b/chrome/browser/autofill/wallet/wallet_address.cc
@@ -0,0 +1,172 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/autofill/wallet/wallet_address.h"
+
+#include "base/logging.h"
+#include "base/values.h"
+
+namespace wallet {
+
+Address::Address() {}
+
+Address::Address(const std::string& country_name_code,
+ const std::string& recipient_name,
+ const std::string& address_line_1,
+ const std::string& address_line_2,
+ const std::string& locality_name,
+ const std::string& administrative_area_name,
+ const std::string& postal_code_number,
+ const std::string& phone_number,
+ const std::string& object_id)
+ : country_name_code_(country_name_code),
+ recipient_name_(recipient_name),
+ address_line_1_(address_line_1),
+ address_line_2_(address_line_2),
+ locality_name_(locality_name),
+ administrative_area_name_(administrative_area_name),
+ postal_code_number_(postal_code_number),
+ phone_number_(phone_number),
+ object_id_(object_id) {}
+
+Address::~Address() {}
+
+scoped_ptr<Address>
+ Address::CreateAddressWithID(const base::DictionaryValue& dictionary) {
+ std::string object_id;
+ if (!dictionary.GetString("id", &object_id)) {
+ DLOG(ERROR) << "Response from Google Wallet missing object id";
+ return scoped_ptr<Address>();
+ }
+
+ std::string country_name_code;
+ if (!dictionary.GetString("postal_address.country_name_code",
+ &country_name_code)) {
+ DLOG(ERROR) << "Response from Google Wallet missing country name";
+ return scoped_ptr<Address>();
+ }
+
+ std::string recipient_name;
+ if (!dictionary.GetString("postal_address.recipient_name",
+ &recipient_name)) {
+ DLOG(ERROR) << "Response from Google Wallet recipient name";
+ return scoped_ptr<Address>();
+ }
+
+ std::string postal_code_number;
+ if (!dictionary.GetString("postal_address.postal_code_number",
+ &postal_code_number)) {
+ DLOG(ERROR) << "Response from Google Wallet missing postal code number";
+ return scoped_ptr<Address>();
+ }
+
+ std::string phone_number;
+ if (!dictionary.GetString("phone_number", &phone_number))
+ DVLOG(1) << "Response from Google Wallet missing phone number";
+
+ std::string address_line_1;
+ std::string address_line_2;
+ const ListValue* address_line_list;
+ if (dictionary.GetList("postal_address.address_line", &address_line_list)) {
+ if (!address_line_list->GetString(0, &address_line_1))
+ DVLOG(1) << "Response from Google Wallet missing address line 1";
+ if (!address_line_list->GetString(1, &address_line_2))
+ DVLOG(1) << "Response from Google Wallet missing address line 2";
+ } else {
+ DVLOG(1) << "Response from Google Wallet missing address lines";
+ }
+
+ std::string locality_name;
+ if (!dictionary.GetString("postal_address.locality_name",
+ &locality_name)) {
+ DVLOG(1) << "Response from Google Wallet missing locality name";
+ }
+
+ std::string administrative_area_name;
+ if (!dictionary.GetString("postal_address.administrative_area_name",
+ &administrative_area_name)) {
+ DVLOG(1) << "Response from Google Wallet missing administrative area name";
+ }
+
+ return scoped_ptr<Address>(new Address(country_name_code,
+ recipient_name ,
+ address_line_1,
+ address_line_2,
+ locality_name,
+ administrative_area_name,
+ postal_code_number,
+ phone_number,
+ object_id));
+}
+
+
+scoped_ptr<Address>
+ Address::CreateDisplayAddress(const base::DictionaryValue& dictionary) {
+ std::string country_code;
+ if (!dictionary.GetString("country_code", &country_code)) {
+ DLOG(ERROR) << "Reponse from Google Wallet missing country code";
+ return scoped_ptr<Address>();
+ }
+
+ std::string name;
+ if (!dictionary.GetString("name", &name)) {
+ DLOG(ERROR) << "Reponse from Google Wallet missing name";
+ return scoped_ptr<Address>();
+ }
+
+ std::string postal_code;
+ if (!dictionary.GetString("postal_code", &postal_code)) {
+ DLOG(ERROR) << "Reponse from Google Wallet missing postal code";
+ return scoped_ptr<Address>();
+ }
+
+ std::string address1;
+ if (!dictionary.GetString("address1", &address1))
+ DVLOG(1) << "Reponse from Google Wallet missing address1";
+
+ std::string address2;
+ if (!dictionary.GetString("address2", &address2))
+ DVLOG(1) << "Reponse from Google Wallet missing address2";
+
+ std::string city;
+ if (!dictionary.GetString("city", &city))
+ DVLOG(1) << "Reponse from Google Wallet missing city";
+
+ std::string state;
+ if (!dictionary.GetString("state", &state))
+ DVLOG(1) << "Reponse from Google Wallet missing state";
+
+ std::string phone_number;
+ if (!dictionary.GetString("phone_number", &phone_number))
+ DVLOG(1) << "Reponse from Google Wallet missing phone number";
+
+ return scoped_ptr<Address>(new Address(country_code,
+ name,
+ address1,
+ address2,
+ city,
+ state,
+ postal_code,
+ phone_number,
+ ""));
+}
+
+bool Address::operator==(const Address& other) const {
+ return country_name_code_ == other.country_name_code_ &&
+ recipient_name_ == other.recipient_name_ &&
+ address_line_1_ == other.address_line_1_ &&
+ address_line_2_ == other.address_line_2_ &&
+ locality_name_ == other.locality_name_ &&
+ administrative_area_name_ == other.administrative_area_name_ &&
+ postal_code_number_ == other.postal_code_number_ &&
+ phone_number_ == other.phone_number_ &&
+ object_id_ == other.object_id_;
+}
+
+bool Address::operator!=(const Address& other) const {
+ return !(*this == other);
+}
+
+} // namespace wallet
+
diff --git a/chrome/browser/autofill/wallet/wallet_address.h b/chrome/browser/autofill/wallet/wallet_address.h
new file mode 100644
index 0000000..4c34afe
--- /dev/null
+++ b/chrome/browser/autofill/wallet/wallet_address.h
@@ -0,0 +1,110 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_AUTOFILL_WALLET_WALLET_ADDRESS_H_
+#define CHROME_BROWSER_AUTOFILL_WALLET_WALLET_ADDRESS_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace wallet {
+
+// TODO(ahutter): This address is a lot like chrome/browser/autofill/address.h.
+// There should be a super class that both extend from to clean up duplicated
+// code. See http://crbug.com/164463.
+
+// Address contains various address fields that have been populated from the
+// user's Online Wallet.
+class Address {
+ public:
+ Address();
+ // TODO(ahutter): Use additional fields (descriptive_name, is_post_box,
+ // is_minimal_address, is_valid, is_default) when SaveToWallet is implemented.
+ // See http://crbug.com/164284.
+ Address(const std::string& country_name_code,
+ const std::string& recipient_name,
+ const std::string& address_line_1,
+ const std::string& address_line_2,
+ const std::string& locality_name,
+ const std::string& administrative_area_name,
+ const std::string& postal_code_number,
+ const std::string& phone_number,
+ const std::string& object_id);
+ ~Address();
+ const std::string& country_name_code() const { return country_name_code_; }
+ const std::string& recipient_name() const { return recipient_name_; }
+ const std::string& address_line_1() const { return address_line_1_; }
+ const std::string& address_line_2() const { return address_line_2_; }
+ const std::string& locality_name() const { return locality_name_; }
+ const std::string& admin_area_name() const {
+ return administrative_area_name_;
+ }
+ const std::string& postal_code_number() const { return postal_code_number_; }
+ const std::string& phone_number() const { return phone_number_; }
+ const std::string& object_id() const { return object_id_; }
+
+ void set_country_name_code(const std::string& country_name_code) {
+ country_name_code_ = country_name_code;
+ }
+ void set_recipient_name(const std::string& recipient_name) {
+ recipient_name_ = recipient_name;
+ }
+ void set_address_line_1(const std::string& address_line_1) {
+ address_line_1_ = address_line_1;
+ }
+ void set_address_line_2(const std::string& address_line_2) {
+ address_line_2_ = address_line_2;
+ }
+ void set_locality_name(const std::string& locality_name) {
+ locality_name_ = locality_name;
+ }
+ void set_admin_area_name(const std::string& administrative_area_name) {
+ administrative_area_name_ = administrative_area_name;
+ }
+ void set_postal_code_number(const std::string& postal_code_number) {
+ postal_code_number_ = postal_code_number;
+ }
+ void set_phone_number(const std::string& phone_number) {
+ phone_number_ = phone_number;
+ }
+ void set_object_id(const std::string& object_id) {
+ object_id_ = object_id;
+ }
+
+ // Returns an empty scoped_ptr if input is invalid or a valid address that is
+ // selectable for Google Wallet use.
+ static scoped_ptr<Address>
+ CreateAddressWithID(const base::DictionaryValue& dictionary);
+
+ // Returns an empty scoped_ptr if input in invalid or a valid address that
+ // can only be used for displaying to the user.
+ static scoped_ptr<Address>
+ CreateDisplayAddress(const base::DictionaryValue& dictionary);
+
+ bool operator==(const Address& other) const;
+ bool operator!=(const Address& other) const;
+
+ private:
+ std::string country_name_code_;
+ std::string recipient_name_;
+ std::string address_line_1_;
+ std::string address_line_2_;
+ std::string locality_name_;
+ std::string administrative_area_name_;
+ std::string postal_code_number_;
+ std::string phone_number_;
+ std::string object_id_;
+ DISALLOW_COPY_AND_ASSIGN(Address);
+};
+
+} // namespace wallet
+
+#endif // CHROME_BROWSER_AUTOFILL_WALLET_WALLET_ADDRESS_H_
+
diff --git a/chrome/browser/autofill/wallet/wallet_address_unittest.cc b/chrome/browser/autofill/wallet/wallet_address_unittest.cc
new file mode 100644
index 0000000..1e05aec
--- /dev/null
+++ b/chrome/browser/autofill/wallet/wallet_address_unittest.cc
@@ -0,0 +1,231 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "chrome/browser/autofill/wallet/wallet_address.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char kAddressMissingObjectId[] =
+ "{"
+ " \"phone_number\":\"phone_number\","
+ " \"postal_address\":"
+ " {"
+ " \"recipient_name\":\"recipient_name\","
+ " \"address_line\":"
+ " ["
+ " \"address_line_1\","
+ " \"address_line_2\""
+ " ],"
+ " \"locality_name\":\"locality_name\","
+ " \"administrative_area_name\":\"administrative_area_name\","
+ " \"postal_code_number\":\"postal_code_number\","
+ " \"country_name_code\":\"country_name_code\""
+ " }"
+ "}";
+
+const char kAddressMissingCountryNameCode[] =
+ "{"
+ " \"id\":\"id\","
+ " \"phone_number\":\"phone_number\","
+ " \"postal_address\":"
+ " {"
+ " \"recipient_name\":\"recipient_name\","
+ " \"address_line\":"
+ " ["
+ " \"address_line_1\","
+ " \"address_line_2\""
+ " ],"
+ " \"locality_name\":\"locality_name\","
+ " \"administrative_area_name\":\"administrative_area_name\","
+ " \"postal_code_number\":\"postal_code_number\""
+ " }"
+ "}";
+
+const char kAddressMissingRecipientName[] =
+ "{"
+ " \"id\":\"id\","
+ " \"phone_number\":\"phone_number\","
+ " \"postal_address\":"
+ " {"
+ " \"address_line\":"
+ " ["
+ " \"address_line_1\","
+ " \"address_line_2\""
+ " ],"
+ " \"locality_name\":\"locality_name\","
+ " \"administrative_area_name\":\"administrative_area_name\","
+ " \"postal_code_number\":\"postal_code_number\","
+ " \"country_name_code\":\"country_name_code\""
+ " }"
+ "}";
+
+const char kAddressMissingPostalCodeNumber[] =
+ "{"
+ " \"id\":\"id\","
+ " \"phone_number\":\"phone_number\","
+ " \"postal_address\":"
+ " {"
+ " \"recipient_name\":\"recipient_name\","
+ " \"address_line\":"
+ " ["
+ " \"address_line_1\","
+ " \"address_line_2\""
+ " ],"
+ " \"locality_name\":\"locality_name\","
+ " \"administrative_area_name\":\"administrative_area_name\","
+ " \"country_name_code\":\"country_name_code\""
+ " }"
+ "}";
+
+const char kValidAddress[] =
+ "{"
+ " \"id\":\"id\","
+ " \"phone_number\":\"phone_number\","
+ " \"postal_address\":"
+ " {"
+ " \"recipient_name\":\"recipient_name\","
+ " \"address_line\":"
+ " ["
+ " \"address_line_1\","
+ " \"address_line_2\""
+ " ],"
+ " \"locality_name\":\"locality_name\","
+ " \"administrative_area_name\":\"administrative_area_name\","
+ " \"country_name_code\":\"country_name_code\","
+ " \"postal_code_number\":\"postal_code_number\""
+ " }"
+ "}";
+
+const char kClientAddressMissingCountryCode[] =
+ "{"
+ " \"name\":\"name\","
+ " \"address1\":\"address1\","
+ " \"address2\":\"address2\","
+ " \"city\":\"city\","
+ " \"state\":\"state\","
+ " \"postal_code\":\"postal_code\","
+ " \"phone_number\":\"phone_number\""
+ "}";
+
+const char kClientAddressMissingPostalCode[] =
+ "{"
+ " \"name\":\"name\","
+ " \"address1\":\"address1\","
+ " \"address2\":\"address2\","
+ " \"city\":\"city\","
+ " \"state\":\"state\","
+ " \"phone_number\":\"phone_number\","
+ " \"country_code\":\"country_code\""
+ "}";
+
+const char kClientAddressMissingName[] =
+ "{"
+ " \"address1\":\"address1\","
+ " \"address2\":\"address2\","
+ " \"city\":\"city\","
+ " \"state\":\"state\","
+ " \"postal_code\":\"postal_code\","
+ " \"phone_number\":\"phone_number\","
+ " \"country_code\":\"country_code\""
+ "}";
+
+const char kClientValidAddress[] =
+ "{"
+ " \"name\":\"name\","
+ " \"address1\":\"address1\","
+ " \"address2\":\"address2\","
+ " \"city\":\"city\","
+ " \"state\":\"state\","
+ " \"postal_code\":\"postal_code\","
+ " \"phone_number\":\"phone_number\","
+ " \"country_code\":\"country_code\""
+ "}";
+
+} // anonymous namespace
+
+namespace wallet {
+
+class WalletAddressTest : public testing::Test {
+ public:
+ WalletAddressTest() {}
+ protected:
+ void SetUpDictionary(const std::string& json) {
+ scoped_ptr<Value> value(base::JSONReader::Read(json));
+ DCHECK(value.get());
+ DCHECK(value->IsType(Value::TYPE_DICTIONARY));
+ dict.reset(static_cast<DictionaryValue*>(value.release()));
+ }
+ scoped_ptr<const DictionaryValue> dict;
+};
+
+TEST_F(WalletAddressTest, CreateAddressWithIDMissingObjectId) {
+ SetUpDictionary(kAddressMissingObjectId);
+ ASSERT_EQ(NULL, Address::CreateAddressWithID(*dict).get());
+}
+
+TEST_F(WalletAddressTest, CreateAddressWithIDMissingCountryNameCode) {
+ SetUpDictionary(kAddressMissingCountryNameCode);
+ ASSERT_EQ(NULL, Address::CreateAddressWithID(*dict).get());
+}
+
+TEST_F(WalletAddressTest, CreateAddressWithIDMissingRecipientName) {
+ SetUpDictionary(kAddressMissingRecipientName);
+ ASSERT_EQ(NULL, Address::CreateAddressWithID(*dict).get());
+}
+
+TEST_F(WalletAddressTest, CreateAddressWithIDMissingPostalCodeNumber) {
+ SetUpDictionary(kAddressMissingPostalCodeNumber);
+ ASSERT_EQ(NULL, Address::CreateAddressWithID(*dict).get());
+}
+
+TEST_F(WalletAddressTest, CreateAddressWithID) {
+ SetUpDictionary(kValidAddress);
+ Address address("country_name_code",
+ "recipient_name",
+ "address_line_1",
+ "address_line_2",
+ "locality_name",
+ "administrative_area_name",
+ "postal_code_number",
+ "phone_number",
+ "id");
+ ASSERT_EQ(address, *Address::CreateAddressWithID(*dict));
+}
+
+TEST_F(WalletAddressTest, CreateDisplayAddressMissingCountryNameCode) {
+ SetUpDictionary(kClientAddressMissingCountryCode);
+ ASSERT_EQ(NULL, Address::CreateDisplayAddress(*dict).get());
+}
+
+TEST_F(WalletAddressTest, CreateDisplayAddressMissingName) {
+ SetUpDictionary(kClientAddressMissingName);
+ ASSERT_EQ(NULL, Address::CreateDisplayAddress(*dict).get());
+}
+
+TEST_F(WalletAddressTest, CreateDisplayAddressMissingPostalCode) {
+ SetUpDictionary(kClientAddressMissingPostalCode);
+ ASSERT_EQ(NULL, Address::CreateDisplayAddress(*dict).get());
+}
+
+TEST_F(WalletAddressTest, CreateDisplayAddress) {
+ SetUpDictionary(kClientValidAddress);
+ Address address("country_code",
+ "name",
+ "address1",
+ "address2",
+ "city",
+ "state",
+ "postal_code",
+ "phone_number",
+ "");
+ ASSERT_EQ(address, *Address::CreateDisplayAddress(*dict));
+}
+
+} // namespace wallet
+
diff --git a/chrome/browser/autofill/wallet/wallet_client.cc b/chrome/browser/autofill/wallet/wallet_client.cc
new file mode 100644
index 0000000..50a4aac
--- /dev/null
+++ b/chrome/browser/autofill/wallet/wallet_client.cc
@@ -0,0 +1,285 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/autofill/wallet/wallet_client.h"
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/string_number_conversions.h"
+#include "base/string_split.h"
+#include "base/stringprintf.h"
+#include "base/values.h"
+#include "chrome/browser/autofill/wallet/cart.h"
+#include "chrome/browser/autofill/wallet/full_wallet.h"
+#include "chrome/browser/autofill/wallet/wallet_address.h"
+#include "chrome/browser/autofill/wallet/wallet_items.h"
+#include "chrome/browser/autofill/wallet/wallet_service_url.h"
+#include "googleurl/src/gurl.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_request_context_getter.h"
+
+namespace {
+
+const char kEncryptOtpBodyFormat[] = "cvv=%s:%s";
+const char kJsonMimeType[] = "application/json";
+const char kApplicationMimeType[] = "application/x-www-form-urlencoded";
+const size_t kMaxBits = 63;
+
+} // anonymous namespace
+
+namespace wallet {
+
+void WalletClient::AcceptLegalDocuments(
+ const std::vector<std::string>& document_ids,
+ const std::string& google_transaction_id,
+ WalletClient::WalletClientObserver* observer) {
+ DCHECK_EQ(request_type_, NO_PENDING_REQUEST);
+
+ request_type_ = ACCEPT_LEGAL_DOCUMENTS;
+
+ DictionaryValue request_dict;
+ request_dict.SetString("api_key", wallet::kApiKey);
+ request_dict.SetString("google_transaction_id", google_transaction_id);
+ ListValue* docs_list = new ListValue();
+ for (std::vector<std::string>::const_iterator it = document_ids.begin();
+ it != document_ids.end();
+ ++it) {
+ docs_list->AppendString(*it);
+ }
+ request_dict.Set("accepted_legal_document", docs_list);
+
+ std::string post_body;
+ base::JSONWriter::Write(&request_dict, &post_body);
+
+ MakeWalletRequest(GetAcceptLegalDocumentsUrl(),
+ post_body,
+ observer,
+ kJsonMimeType);
+}
+
+void WalletClient::EncryptOtp(
+ const void* otp,
+ size_t length,
+ WalletClient::WalletClientObserver* observer) {
+ DCHECK_EQ(request_type_, NO_PENDING_REQUEST);
+ size_t num_bits = length * 8;
+ DCHECK_LT(num_bits, kMaxBits);
+
+ request_type_ = ENCRYPT_OTP;
+
+ std::string post_body = StringPrintf(kEncryptOtpBodyFormat,
+ base::HexEncode(&num_bits, 1).c_str(),
+ base::HexEncode(otp, length).c_str());
+
+ MakeWalletRequest(GetSecureUrl(), post_body, observer, kApplicationMimeType);
+}
+
+void WalletClient::GetFullWallet(
+ const std::string& instrument_id,
+ const std::string& address_id,
+ const std::string& merchant_domain,
+ const Cart& cart,
+ const std::string& google_transaction_id,
+ const std::string& encrypted_otp,
+ const std::string& session_material,
+ WalletClient::WalletClientObserver* observer) {
+ DCHECK_EQ(request_type_, NO_PENDING_REQUEST);
+
+ request_type_ = GET_FULL_WALLET;
+
+ DictionaryValue request_dict;
+ request_dict.SetString("api_key", wallet::kApiKey);
+ request_dict.SetString("risk_params", GetRiskParams());
+ request_dict.SetString("selected_instrument_id", instrument_id);
+ request_dict.SetString("selected_address_id", address_id);
+ request_dict.SetString("merchant_domain", merchant_domain);
+ request_dict.SetString("google_transaction_id", google_transaction_id);
+ request_dict.Set("cart", cart.ToDictionary().release());
+ request_dict.SetString("encrypted_otp", encrypted_otp);
+ request_dict.SetString("session_material", session_material);
+
+ std::string post_body;
+ base::JSONWriter::Write(&request_dict, &post_body);
+
+ MakeWalletRequest(GetGetFullWalletUrl(), post_body, observer, kJsonMimeType);
+}
+
+void WalletClient::GetWalletItems(
+ WalletClient::WalletClientObserver* observer) {
+ DCHECK_EQ(request_type_, NO_PENDING_REQUEST);
+
+ request_type_ = GET_WALLET_ITEMS;
+
+ DictionaryValue request_dict;
+ request_dict.SetString("api_key", wallet::kApiKey);
+ request_dict.SetString("risk_params", GetRiskParams());
+
+ std::string post_body;
+ base::JSONWriter::Write(&request_dict, &post_body);
+
+ MakeWalletRequest(GetGetWalletItemsUrl(), post_body, observer, kJsonMimeType);
+}
+
+void WalletClient::SendExtendedAutofillStatus(
+ bool success,
+ const std::string& merchant_domain,
+ const std::string& reason,
+ const std::string& google_transaction_id,
+ WalletClient::WalletClientObserver* observer) {
+ DCHECK_EQ(request_type_, NO_PENDING_REQUEST);
+
+ request_type_ = SEND_STATUS;
+
+ DictionaryValue request_dict;
+ request_dict.SetString("api_key", wallet::kApiKey);
+ request_dict.SetBoolean("success", success);
+ request_dict.SetString("hostname", merchant_domain);
+ if (!success) {
+ // TODO(ahutter): Probably want to do some checks on reason.
+ request_dict.SetString("reason", reason);
+ }
+ request_dict.SetString("google_transaction_id", google_transaction_id);
+
+ std::string post_body;
+ base::JSONWriter::Write(&request_dict, &post_body);
+
+ MakeWalletRequest(GetSendStatusUrl(), post_body, observer, kJsonMimeType);
+}
+
+void WalletClient::MakeWalletRequest(
+ const GURL& url,
+ const std::string& post_body,
+ WalletClient::WalletClientObserver* observer,
+ const std::string& content_type) {
+ DCHECK(!request_.get()) << "Tried to fetch two things at once!";
+ DCHECK(observer);
+
+ observer_ = observer;
+
+ request_.reset(net::URLFetcher::Create(
+ 0, url, net::URLFetcher::POST, this));
+ request_->SetRequestContext(context_getter_);
+ DVLOG(1) << "Request body: " << post_body;
+ request_->SetUploadData(content_type, post_body);
+ request_->Start();
+}
+
+// TODO(ahutter): Add manual retry logic if it's necessary.
+void WalletClient::OnURLFetchComplete(
+ const net::URLFetcher* source) {
+ scoped_ptr<net::URLFetcher> old_request = request_.Pass();
+ DCHECK_EQ(source, old_request.get());
+
+ DVLOG(1) << "Got response from " << source->GetOriginalURL();
+
+ std::string data;
+ scoped_ptr<DictionaryValue> response_dict;
+ int response_code = source->GetResponseCode();
+ switch (response_code) {
+ // HTTP_BAD_REQUEST means the arguments are invalid. No point retrying.
+ case net::HTTP_BAD_REQUEST: {
+ request_type_ = NO_PENDING_REQUEST;
+ observer_->OnWalletError();
+ return;
+ }
+ // HTTP_OK holds a valid response and HTTP_INTERNAL_SERVER_ERROR holds an
+ // error code and message for the user.
+ case net::HTTP_OK:
+ case net::HTTP_INTERNAL_SERVER_ERROR: {
+ source->GetResponseAsString(&data);
+ DVLOG(1) << "Response body: " << data;
+ scoped_ptr<Value> message_value(base::JSONReader::Read(data));
+ if (message_value.get() &&
+ message_value->IsType(Value::TYPE_DICTIONARY)) {
+ response_dict.reset(
+ static_cast<DictionaryValue*>(message_value.release()));
+ }
+ if (response_code == net::HTTP_INTERNAL_SERVER_ERROR) {
+ request_type_ = NO_PENDING_REQUEST;
+ // TODO(ahutter): Do something with the response. See
+ // http://crbug.com/164410.
+ observer_->OnWalletError();
+ return;
+ }
+ break;
+ }
+ // Anything else is an error.
+ default: {
+ request_type_ = NO_PENDING_REQUEST;
+ observer_->OnNetworkError(response_code);
+ return;
+ }
+ }
+
+ RequestType type = request_type_;
+ request_type_ = NO_PENDING_REQUEST;
+
+ switch (type) {
+ case ACCEPT_LEGAL_DOCUMENTS: {
+ observer_->OnAcceptLegalDocuments();
+ break;
+ }
+ case SEND_STATUS: {
+ observer_->OnSendExtendedAutofillStatus();
+ break;
+ }
+ case ENCRYPT_OTP: {
+ if (!data.empty()) {
+ std::vector<std::string> splits;
+ base::SplitString(data, '|', &splits);
+ if (splits.size() == 2)
+ observer_->OnEncryptOtp(splits[1], splits[0]);
+ else
+ observer_->OnNetworkError(response_code);
+ } else {
+ observer_->OnWalletError();
+ }
+ break;
+ }
+ case GET_FULL_WALLET: {
+ if (response_dict.get()) {
+ scoped_ptr<FullWallet> full_wallet(
+ FullWallet::CreateFullWallet(*response_dict));
+ if (full_wallet.get())
+ observer_->OnGetFullWallet(full_wallet.release());
+ else
+ observer_->OnNetworkError(response_code);
+ } else {
+ observer_->OnWalletError();
+ }
+ break;
+ }
+ case GET_WALLET_ITEMS: {
+ if (response_dict.get()) {
+ scoped_ptr<WalletItems> wallet_items(
+ WalletItems::CreateWalletItems(*response_dict));
+ if (wallet_items.get())
+ observer_->OnGetWalletItems(wallet_items.release());
+ else
+ observer_->OnNetworkError(response_code);
+ } else {
+ observer_->OnWalletError();
+ }
+ break;
+ }
+ default: {
+ NOTREACHED();
+ }
+ }
+}
+
+WalletClient::WalletClient(net::URLRequestContextGetter* context_getter)
+ : context_getter_(context_getter),
+ observer_(NULL),
+ request_type_(NO_PENDING_REQUEST) {
+ DCHECK(context_getter);
+}
+
+WalletClient::~WalletClient() {}
+
+} // namespace wallet
+
diff --git a/chrome/browser/autofill/wallet/wallet_client.h b/chrome/browser/autofill/wallet/wallet_client.h
new file mode 100644
index 0000000..130499c
--- /dev/null
+++ b/chrome/browser/autofill/wallet/wallet_client.h
@@ -0,0 +1,152 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_AUTOFILL_WALLET_WALLET_CLIENT_H_
+#define CHROME_BROWSER_AUTOFILL_WALLET_WALLET_CLIENT_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "net/url_request/url_fetcher_delegate.h"
+
+class GURL;
+
+namespace net {
+class URLRequestContextGetter;
+class URLFetcher;
+}
+
+namespace wallet {
+
+class Address;
+class Cart;
+class FullWallet;
+class WalletItems;
+
+// WalletClient is responsible for making calls to the Online Wallet backend on
+// the user's behalf.
+class WalletClient : public net::URLFetcherDelegate {
+ public:
+ // WalletClientObserver is to be implemented any classes making calls with
+ // WalletClient. The appropriate callback method will be called on
+ // WalletClientObserver with the response from the Online Wallet backend.
+ class WalletClientObserver {
+ public:
+ // Called when an AcceptLegalDocuments request finishes successfully.
+ virtual void OnAcceptLegalDocuments() = 0;
+
+ // Called when an EncryptOtp request finishes successfully.
+ virtual void OnEncryptOtp(const std::string& encrypted_otp,
+ const std::string& session_material) = 0;
+
+ // Called when a GetFullWallet request finishes successfully. Caller owns
+ // the input pointer.
+ virtual void OnGetFullWallet(FullWallet* full_wallet) = 0;
+
+ // Called when a GetWalletItems request finishes successfully. Caller owns
+ // the input pointer.
+ virtual void OnGetWalletItems(WalletItems* wallet_items) = 0;
+
+ // Called when a SendExtendedAutofillStatus request finishes successfully.
+ virtual void OnSendExtendedAutofillStatus() = 0;
+
+ // TODO(ahutter): This is going to need more arguments, probably an error
+ // code and a message for the user.
+ // Called when a request fails due to an Online Wallet error.
+ virtual void OnWalletError() = 0;
+
+ // Called when a request fails due to a network error or if the response was
+ // invalid.
+ virtual void OnNetworkError(int response_code) = 0;
+
+ protected:
+ virtual ~WalletClientObserver() {}
+ };
+ explicit WalletClient(net::URLRequestContextGetter* context_getter);
+ virtual ~WalletClient();
+
+ // GetWalletItems retrieves the user's online wallet. The WalletItems
+ // returned may require additional action such as presenting legal documents
+ // to the user to be accepted.
+ void GetWalletItems(WalletClientObserver* observer);
+
+ // The GetWalletItems call to the Online Wallet backend may require the user
+ // to accept various legal documents before a FullWallet can be generated.
+ // The |document_ids| and |google_transaction_id| are provided in the response
+ // to the GetWalletItems call.
+ void AcceptLegalDocuments(const std::vector<std::string>& document_ids,
+ const std::string& google_transaction_id,
+ WalletClientObserver* observer);
+
+ // Before calling GetFullWallet, the client must encrypt a one time pad,
+ // |otp|, of crytographically secure random bytes. These bytes must be
+ // generated using crypto/random.h.
+ void EncryptOtp(const void* otp,
+ size_t length,
+ WalletClientObserver* observer);
+
+ // GetFullWallet retrieves the a FullWallet for the user. |instrument_id| and
+ // |adddress_id| should have been selected by the user in some UI,
+ // |merchant_domain| should come from the BrowserContext, the |cart|
+ // information will have been provided by the browser, |google_transaction_id|
+ // is the same one that GetWalletItems returns, and |encrypted_otp| and
+ // |session_material| are the results of the EncryptOtp call.
+ void GetFullWallet(const std::string& instrument_id,
+ const std::string& address_id,
+ const std::string& merchant_domain,
+ const Cart& cart,
+ const std::string& google_transaction_id,
+ const std::string& encrypted_otp,
+ const std::string& session_material,
+ WalletClientObserver* observer);
+
+ // SendExtendedAutofillStatus is used for tracking the success of
+ // WalletClient. |success| is a flag for success, |merchant_domain| is the
+ // domain where the purchase occured, if the purchase was not successful
+ // |reason| is populated, and |google_transaction_id| is the same as the one
+ // provided by GetWalletItems.
+ void SendExtendedAutofillStatus(bool success,
+ const std::string& merchant_domain,
+ const std::string& reason,
+ const std::string& google_transaction_id,
+ WalletClientObserver* observer);
+
+ private:
+ // TODO(ahutter): Implement this.
+ std::string GetRiskParams() { return ""; }
+
+ enum RequestType {
+ NO_PENDING_REQUEST,
+ ACCEPT_LEGAL_DOCUMENTS,
+ ENCRYPT_OTP,
+ GET_FULL_WALLET,
+ GET_WALLET_ITEMS,
+ SEND_STATUS,
+ };
+
+ void MakeWalletRequest(const GURL& url,
+ const std::string& post_body,
+ WalletClient::WalletClientObserver* observer,
+ const std::string& content_type);
+ virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE;
+
+ // The context for the request. Ensures the gdToken cookie is set as a header
+ // in the requests to Online Wallet if it is present.
+ scoped_refptr<net::URLRequestContextGetter> context_getter_;
+ // Observer class that has its various On* methods called based on the results
+ // of a request to Online Wallet.
+ WalletClientObserver* observer_;
+ // The current request object.
+ scoped_ptr<net::URLFetcher> request_;
+ // The type of the current request. Must be NO_PENDING_REQUEST for a request
+ // to be initiated as only one request may be running at a given time.
+ RequestType request_type_;
+ DISALLOW_COPY_AND_ASSIGN(WalletClient);
+};
+
+} // namespace wallet
+
+#endif // CHROME_BROWSER_AUTOFILL_WALLET_WALLET_CLIENT_H_
+
diff --git a/chrome/browser/autofill/wallet/wallet_client_unittest.cc b/chrome/browser/autofill/wallet/wallet_client_unittest.cc
new file mode 100644
index 0000000..0fbebd4
--- /dev/null
+++ b/chrome/browser/autofill/wallet/wallet_client_unittest.cc
@@ -0,0 +1,396 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/logging.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "chrome/browser/autofill/wallet/cart.h"
+#include "chrome/browser/autofill/wallet/full_wallet.h"
+#include "chrome/browser/autofill/wallet/wallet_client.h"
+#include "chrome/browser/autofill/wallet/wallet_items.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/test/test_browser_thread.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_request_status.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char kGoogleTransactionId[] = "google-transaction-id";
+
+const char kGetFullWalletValidResponse[] =
+ "{"
+ " \"expiration_month\":12,"
+ " \"expiration_year\":2012,"
+ " \"iin\":\"iin\","
+ " \"rest\":\"rest\","
+ " \"billing_address\":"
+ " {"
+ " \"id\":\"id\","
+ " \"phone_number\":\"phone_number\","
+ " \"postal_address\":"
+ " {"
+ " \"recipient_name\":\"recipient_name\","
+ " \"address_line\":"
+ " ["
+ " \"address_line_1\","
+ " \"address_line_2\""
+ " ],"
+ " \"locality_name\":\"locality_name\","
+ " \"administrative_area_name\":\"administrative_area_name\","
+ " \"postal_code_number\":\"postal_code_number\","
+ " \"country_name_code\":\"country_name_code\""
+ " }"
+ " },"
+ " \"shipping_address\":"
+ " {"
+ " \"id\":\"ship_id\","
+ " \"phone_number\":\"ship_phone_number\","
+ " \"postal_address\":"
+ " {"
+ " \"recipient_name\":\"ship_recipient_name\","
+ " \"address_line\":"
+ " ["
+ " \"ship_address_line_1\","
+ " \"ship_address_line_2\""
+ " ],"
+ " \"locality_name\":\"ship_locality_name\","
+ " \"administrative_area_name\":\"ship_administrative_area_name\","
+ " \"postal_code_number\":\"ship_postal_code_number\","
+ " \"country_name_code\":\"ship_country_name_code\""
+ " }"
+ " },"
+ " \"required_action\":"
+ " ["
+ " ]"
+ "}";
+
+const char kGetWalletItemsValidResponse[] =
+ "{"
+ " \"required_action\":"
+ " ["
+ " ],"
+ " \"google_transaction_id\":\"google_transaction_id\","
+ " \"instrument\":"
+ " ["
+ " {"
+ " \"descriptive_name\":\"descriptive_name\","
+ " \"type\":\"VISA\","
+ " \"supported_currency\":\"currency_code\","
+ " \"last_four_digits\":\"last_four_digits\","
+ " \"expiration_month\":12,"
+ " \"expiration_year\":2012,"
+ " \"brand\":\"monkeys\","
+ " \"billing_address\":"
+ " {"
+ " \"name\":\"name\","
+ " \"address1\":\"address1\","
+ " \"address2\":\"address2\","
+ " \"city\":\"city\","
+ " \"state\":\"state\","
+ " \"postal_code\":\"postal_code\","
+ " \"phone_number\":\"phone_number\","
+ " \"country_code\":\"country_code\""
+ " },"
+ " \"status\":\"VALID\","
+ " \"object_id\":\"default_instrument_id\""
+ " }"
+ " ],"
+ " \"default_instrument_id\":\"default_instrument_id\","
+ " \"address\":"
+ " ["
+ " ],"
+ " \"default_address_id\":\"default_address_id\","
+ " \"required_legal_document\":"
+ " ["
+ " ]"
+ "}";
+
+// The JSON below is used to test against the request payload being sent to
+// Online Wallet. It's indented differently since JSONWriter creates compact
+// JSON from DictionaryValues.
+
+const char kAcceptLegalDocumentsValidRequest[] =
+ "{"
+ "\"accepted_legal_document\":"
+ "["
+ "\"doc_1\","
+ "\"doc_2\""
+ "],"
+ "\"api_key\":\"abcdefg\","
+ "\"google_transaction_id\":\"google-transaction-id\""
+ "}";
+
+const char kGetFullWalletValidRequest[] =
+ "{"
+ "\"api_key\":\"abcdefg\","
+ "\"cart\":"
+ "{"
+ "\"currency_code\":\"currency_code\","
+ "\"total_price\":\"currency_code\""
+ "},"
+ "\"encrypted_otp\":\"encrypted_otp\","
+ "\"google_transaction_id\":\"google_transaction_id\","
+ "\"merchant_domain\":\"merchant_domain\","
+ "\"risk_params\":\"\","
+ "\"selected_address_id\":\"shipping_address_id\","
+ "\"selected_instrument_id\":\"instrument_id\","
+ "\"session_material\":\"session_material\""
+ "}";
+
+const char kGetWalletItemsValidRequest[] =
+ "{"
+ "\"api_key\":\"abcdefg\","
+ "\"risk_params\":\"\""
+ "}";
+
+const char kSendExtendedAutofillStatusOfSuccessValidRequest[] =
+ "{"
+ "\"api_key\":\"abcdefg\","
+ "\"google_transaction_id\":\"google_transaction_id\","
+ "\"hostname\":\"hostname\","
+ "\"success\":true"
+ "}";
+
+const char kSendExtendedAutofillStatusOfFailureValidRequest[] =
+ "{"
+ "\"api_key\":\"abcdefg\","
+ "\"google_transaction_id\":\"google_transaction_id\","
+ "\"hostname\":\"hostname\","
+ "\"reason\":\"CANNOT_PROCEED\","
+ "\"success\":false"
+ "}";
+
+} // anonymous namespace
+
+namespace wallet {
+
+class WalletClientTest : public testing::Test {
+ public:
+ WalletClientTest() : io_thread_(content::BrowserThread::IO) {}
+
+ virtual void SetUp() {
+ io_thread_.StartIOThread();
+ profile_.CreateRequestContext();
+ }
+
+ virtual void TearDown() {
+ profile_.ResetRequestContext();
+ io_thread_.Stop();
+ }
+
+ protected:
+ TestingProfile profile_;
+
+ private:
+ // The profile's request context must be released on the IO thread.
+ content::TestBrowserThread io_thread_;
+};
+
+class MockWalletClientObserver
+ : public wallet::WalletClient::WalletClientObserver {
+ public:
+ MockWalletClientObserver() {}
+ ~MockWalletClientObserver() {}
+
+ MOCK_METHOD0(OnAcceptLegalDocuments, void());
+ MOCK_METHOD2(OnEncryptOtp, void(const std::string& encrypted_otp,
+ const std::string& session_material));
+ MOCK_METHOD1(OnGetFullWallet, void(FullWallet* full_wallet));
+ MOCK_METHOD1(OnGetWalletItems, void(WalletItems* wallet_items));
+ MOCK_METHOD0(OnSendExtendedAutofillStatus, void());
+ MOCK_METHOD0(OnWalletError, void());
+ MOCK_METHOD1(OnNetworkError, void(int response_code));
+};
+
+// TODO(ahutter): Implement API compatibility tests. See
+// http://crbug.com/164465.
+
+// TODO(ahutter): Improve this when the error body is captured. See
+// http://crbug.com/164410.
+TEST_F(WalletClientTest, WalletErrorOnExpectedVoidResponse) {
+ MockWalletClientObserver observer;
+ EXPECT_CALL(observer, OnWalletError()).Times(1);
+
+ net::TestURLFetcherFactory factory;
+
+ WalletClient wallet_client(profile_.GetRequestContext());
+ wallet_client.SendExtendedAutofillStatus(true, "", "", "", &observer);
+ net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
+ DCHECK(fetcher);
+ fetcher->set_response_code(net::HTTP_INTERNAL_SERVER_ERROR);
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+}
+
+// TODO(ahutter): Improve this when the error body is captured. See
+// http://crbug.com/164410.
+TEST_F(WalletClientTest, WalletErrorOnExpectedResponse) {
+ MockWalletClientObserver observer;
+ EXPECT_CALL(observer, OnWalletError()).Times(1);
+
+ net::TestURLFetcherFactory factory;
+
+ WalletClient wallet_client(profile_.GetRequestContext());
+ Cart cart("currency_code", "currency_code");
+ wallet_client.GetFullWallet("", "", "", cart, "", "", "", &observer);
+ net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
+ DCHECK(fetcher);
+ fetcher->set_response_code(net::HTTP_INTERNAL_SERVER_ERROR);
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+}
+
+TEST_F(WalletClientTest, NetworkFailureOnExpectedVoidResponse) {
+ MockWalletClientObserver observer;
+ EXPECT_CALL(observer, OnNetworkError(net::HTTP_UNAUTHORIZED)).Times(1);
+
+ net::TestURLFetcherFactory factory;
+
+ WalletClient wallet_client(profile_.GetRequestContext());
+ wallet_client.SendExtendedAutofillStatus(true, "", "", "", &observer);
+ net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
+ DCHECK(fetcher);
+ fetcher->set_response_code(net::HTTP_UNAUTHORIZED);
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+}
+
+TEST_F(WalletClientTest, NetworkFailureOnExpectedResponse) {
+ MockWalletClientObserver observer;
+ EXPECT_CALL(observer, OnNetworkError(net::HTTP_UNAUTHORIZED)).Times(1);
+
+ net::TestURLFetcherFactory factory;
+
+ WalletClient wallet_client(profile_.GetRequestContext());
+ Cart cart("currency_code", "currency_code");
+ wallet_client.GetFullWallet("", "", "", cart, "", "", "", &observer);
+ net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
+ DCHECK(fetcher);
+ fetcher->set_response_code(net::HTTP_UNAUTHORIZED);
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+}
+
+TEST_F(WalletClientTest, RequestError) {
+ MockWalletClientObserver observer;
+ EXPECT_CALL(observer, OnWalletError()).Times(1);
+
+ net::TestURLFetcherFactory factory;
+
+ WalletClient wallet_client(profile_.GetRequestContext());
+ wallet_client.SendExtendedAutofillStatus(true, "", "", "", &observer);
+ net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
+ DCHECK(fetcher);
+ fetcher->set_response_code(net::HTTP_BAD_REQUEST);
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+}
+
+// TODO(ahutter): Add test for EncryptOtp.
+// TODO(ahutter): Add retry and failure tests for EncryptOtp, GetWalletItems,
+// GetFullWallet for when data is missing or invalid.
+
+TEST_F(WalletClientTest, GetFullWallet) {
+ MockWalletClientObserver observer;
+ EXPECT_CALL(observer, OnGetFullWallet(testing::NotNull())).Times(1);
+
+ net::TestURLFetcherFactory factory;
+
+ WalletClient wallet_client(profile_.GetRequestContext());
+ Cart cart("currency_code", "currency_code");
+ wallet_client.GetFullWallet("instrument_id",
+ "shipping_address_id",
+ "merchant_domain",
+ cart,
+ "google_transaction_id",
+ "encrypted_otp",
+ "session_material",
+ &observer);
+ net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
+ DCHECK(fetcher);
+ ASSERT_EQ(kGetFullWalletValidRequest, fetcher->upload_data());
+ fetcher->set_response_code(net::HTTP_OK);
+ fetcher->SetResponseString(kGetFullWalletValidResponse);
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+}
+
+TEST_F(WalletClientTest, AcceptLegalDocuments) {
+ MockWalletClientObserver observer;
+ EXPECT_CALL(observer, OnAcceptLegalDocuments()).Times(1);
+
+ net::TestURLFetcherFactory factory;
+
+ WalletClient wallet_client(profile_.GetRequestContext());
+ std::vector<std::string> doc_ids;
+ doc_ids.push_back("doc_1");
+ doc_ids.push_back("doc_2");
+ wallet_client.AcceptLegalDocuments(doc_ids,
+ kGoogleTransactionId,
+ &observer);
+ net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
+ DCHECK(fetcher);
+ ASSERT_EQ(kAcceptLegalDocumentsValidRequest, fetcher->upload_data());
+ fetcher->set_response_code(net::HTTP_OK);
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+}
+
+TEST_F(WalletClientTest, GetWalletItems) {
+ MockWalletClientObserver observer;
+ EXPECT_CALL(observer, OnGetWalletItems(testing::NotNull())).Times(1);
+
+ net::TestURLFetcherFactory factory;
+
+ WalletClient wallet_client(profile_.GetRequestContext());
+ wallet_client.GetWalletItems(&observer);
+ net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
+ DCHECK(fetcher);
+ ASSERT_EQ(kGetWalletItemsValidRequest, fetcher->upload_data());
+ fetcher->set_response_code(net::HTTP_OK);
+ fetcher->SetResponseString(kGetWalletItemsValidResponse);
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+}
+
+TEST_F(WalletClientTest, SendExtendedAutofillOfStatusSuccess) {
+ MockWalletClientObserver observer;
+ EXPECT_CALL(observer, OnSendExtendedAutofillStatus()).Times(1);
+
+ net::TestURLFetcherFactory factory;
+
+ WalletClient wallet_client(profile_.GetRequestContext());
+ wallet_client.SendExtendedAutofillStatus(true,
+ "hostname",
+ "",
+ "google_transaction_id",
+ &observer);
+ net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
+ DCHECK(fetcher);
+ ASSERT_EQ(kSendExtendedAutofillStatusOfSuccessValidRequest,
+ fetcher->upload_data());
+ fetcher->set_response_code(net::HTTP_OK);
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+}
+
+TEST_F(WalletClientTest, SendExtendedAutofillStatusOfFailure) {
+ MockWalletClientObserver observer;
+ EXPECT_CALL(observer, OnSendExtendedAutofillStatus()).Times(1);
+
+ net::TestURLFetcherFactory factory;
+
+ WalletClient wallet_client(profile_.GetRequestContext());
+ wallet_client.SendExtendedAutofillStatus(false,
+ "hostname",
+ "CANNOT_PROCEED",
+ "google_transaction_id",
+ &observer);
+ net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
+ DCHECK(fetcher);
+ ASSERT_EQ(kSendExtendedAutofillStatusOfFailureValidRequest,
+ fetcher->upload_data());
+ fetcher->set_response_code(net::HTTP_OK);
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+}
+
+} // namespace wallet
+
diff --git a/chrome/browser/autofill/wallet/wallet_items.cc b/chrome/browser/autofill/wallet/wallet_items.cc
new file mode 100644
index 0000000..3530772
--- /dev/null
+++ b/chrome/browser/autofill/wallet/wallet_items.cc
@@ -0,0 +1,360 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/autofill/wallet/wallet_items.h"
+
+#include "base/logging.h"
+#include "base/values.h"
+
+namespace {
+
+wallet::WalletItems::MaskedInstrument::Type
+ TypeFromString(const std::string& type_string) {
+ if (type_string == "VISA")
+ return wallet::WalletItems::MaskedInstrument::VISA;
+ if (type_string == "MASTER_CARD")
+ return wallet::WalletItems::MaskedInstrument::MASTER_CARD;
+ if (type_string == "AMEX")
+ return wallet::WalletItems::MaskedInstrument::AMEX;
+ if (type_string == "DISCOVER")
+ return wallet::WalletItems::MaskedInstrument::DISCOVER;
+ if (type_string == "SOLO")
+ return wallet::WalletItems::MaskedInstrument::SOLO;
+ if (type_string == "MAESTRO")
+ return wallet::WalletItems::MaskedInstrument::MAESTRO;
+ if (type_string == "SWITCH")
+ return wallet::WalletItems::MaskedInstrument::SWITCH;
+ return wallet::WalletItems::MaskedInstrument::UNKNOWN;
+}
+
+wallet::WalletItems::MaskedInstrument::Status
+ StatusFromString(const std::string& status_string) {
+ if (status_string == "PENDING")
+ return wallet::WalletItems::MaskedInstrument::PENDING;
+ if (status_string == "VALID")
+ return wallet::WalletItems::MaskedInstrument::VALID;
+ if (status_string == "DECLINED")
+ return wallet::WalletItems::MaskedInstrument::DECLINED;
+ if (status_string == "UNSUPPORTED_COUNTRY")
+ return wallet::WalletItems::MaskedInstrument::UNSUPPORTED_COUNTRY;
+ if (status_string == "EXPIRED")
+ return wallet::WalletItems::MaskedInstrument::EXPIRED;
+ if (status_string == "BILLING_INCOMPLETE")
+ return wallet::WalletItems::MaskedInstrument::BILLING_INCOMPLETE;
+ return wallet::WalletItems::MaskedInstrument::INAPPLICABLE;
+}
+
+} // anonymous namespace
+
+namespace wallet {
+
+WalletItems::MaskedInstrument::MaskedInstrument(
+ const std::string& descriptive_name,
+ const WalletItems::MaskedInstrument::Type& type,
+ const std::vector<std::string>& supported_currencies,
+ const std::string& last_four_digits,
+ int expiration_month,
+ int expiration_year,
+ const std::string& brand,
+ scoped_ptr<Address> address,
+ const WalletItems::MaskedInstrument::Status& status,
+ const std::string& object_id)
+ : descriptive_name_(descriptive_name),
+ type_(type),
+ supported_currencies_(supported_currencies),
+ last_four_digits_(last_four_digits),
+ expiration_month_(expiration_month),
+ expiration_year_(expiration_year),
+ brand_(brand),
+ address_(address.Pass()),
+ status_(status),
+ object_id_(object_id) {
+ DCHECK(address_.get());
+}
+
+WalletItems::MaskedInstrument::~MaskedInstrument() {}
+
+scoped_ptr<WalletItems::MaskedInstrument>
+ WalletItems::MaskedInstrument::CreateMaskedInstrument(
+ const base::DictionaryValue& dictionary) {
+ std::string type_string;
+ Type type;
+ if (dictionary.GetString("type", &type_string)) {
+ type = TypeFromString(type_string);
+ } else {
+ DLOG(ERROR) << "Response from Google Wallet missing card type";
+ return scoped_ptr<MaskedInstrument>();
+ }
+
+ std::string last_four_digits;
+ if (!dictionary.GetString("last_four_digits", &last_four_digits)) {
+ DLOG(ERROR) << "Response from Google Wallet missing last four digits";
+ return scoped_ptr<MaskedInstrument>();
+ }
+
+ std::string status_string;
+ Status status;
+ if (dictionary.GetString("status", &status_string)) {
+ status = StatusFromString(status_string);
+ } else {
+ DLOG(ERROR) << "Response from Google Wallet missing status";
+ return scoped_ptr<MaskedInstrument>();
+ }
+
+ std::string object_id;
+ if (!dictionary.GetString("object_id", &object_id)) {
+ DLOG(ERROR) << "Response from Google Wallet missing object id";
+ return scoped_ptr<MaskedInstrument>();
+ }
+
+ const DictionaryValue* address_dict;
+ if (!dictionary.GetDictionary("billing_address", &address_dict)) {
+ DLOG(ERROR) << "Response from Google wallet missing address";
+ return scoped_ptr<MaskedInstrument>();
+ }
+ scoped_ptr<Address> address = Address::CreateDisplayAddress(*address_dict);
+
+ if (!address.get()) {
+ DLOG(ERROR) << "Response from Google wallet contained malformed address";
+ return scoped_ptr<MaskedInstrument>();
+ }
+
+ std::vector<std::string> supported_currencies;
+ const ListValue* supported_currency_list;
+ if (dictionary.GetList("supported_currency", &supported_currency_list)) {
+ for (size_t i = 0; i < supported_currency_list->GetSize(); ++i) {
+ std::string currency;
+ if (supported_currency_list->GetString(i, &currency))
+ supported_currencies.push_back(currency);
+ }
+ } else {
+ DVLOG(1) << "Response from Google Wallet missing supported currency";
+ }
+
+ int expiration_month;
+ if (!dictionary.GetInteger("expiration_month", &expiration_month))
+ DVLOG(1) << "Response from Google Wallet missing expiration month";
+
+ int expiration_year;
+ if (!dictionary.GetInteger("expiration_year", &expiration_year))
+ DVLOG(1) << "Response from Google Wallet missing expiration year";
+
+ std::string brand;
+ if (!dictionary.GetString("brand", &brand))
+ DVLOG(1) << "Response from Google Wallet missing brand";
+
+ std::string descriptive_name;
+ if (!dictionary.GetString("descriptive_name", &descriptive_name))
+ DVLOG(1) << "Response from Google Wallet missing descriptive name";
+
+ return scoped_ptr<MaskedInstrument>(new MaskedInstrument(descriptive_name,
+ type,
+ supported_currencies,
+ last_four_digits,
+ expiration_month,
+ expiration_year,
+ brand,
+ address.Pass(),
+ status,
+ object_id));
+}
+
+bool WalletItems::MaskedInstrument::operator==(
+ const WalletItems::MaskedInstrument& other) const {
+ if (descriptive_name_ != other.descriptive_name_)
+ return false;
+ if (type_ != other.type_)
+ return false;
+ if (supported_currencies_ != other.supported_currencies_)
+ return false;
+ if (last_four_digits_ != other.last_four_digits_)
+ return false;
+ if (expiration_month_ != other.expiration_month_)
+ return false;
+ if (expiration_year_ != other.expiration_year_)
+ return false;
+ if (brand_ != other.brand_)
+ return false;
+ if (address_.get()) {
+ if (other.address_.get()) {
+ if (*address_.get() != *other.address_.get())
+ return false;
+ } else {
+ return false;
+ }
+ } else if (other.address_.get()) {
+ return false;
+ }
+ if (status_ != other.status_)
+ return false;
+ if (object_id_ != other.object_id_)
+ return false;
+ return true;
+}
+
+bool WalletItems::MaskedInstrument::operator!=(
+ const WalletItems::MaskedInstrument& other) const {
+ return !(*this == other);
+}
+
+WalletItems::LegalDocument::LegalDocument(const std::string& document_id,
+ const std::string& display_name,
+ const std::string& document_body)
+ : document_id_(document_id),
+ display_name_(display_name),
+ document_body_(document_body) {}
+
+WalletItems::LegalDocument::~LegalDocument() {}
+
+scoped_ptr<WalletItems::LegalDocument>
+ WalletItems::LegalDocument::CreateLegalDocument(
+ const base::DictionaryValue& dictionary) {
+ std::string document_id;
+ if (!dictionary.GetString("legal_document_id", &document_id)) {
+ DLOG(ERROR) << "Response from Google Wallet missing legal document id";
+ return scoped_ptr<LegalDocument>();
+ }
+
+ std::string display_name;
+ if (!dictionary.GetString("display_name", &display_name)) {
+ DLOG(ERROR) << "Response from Google Wallet missing display name";
+ return scoped_ptr<LegalDocument>();
+ }
+
+ std::string document_body;
+ if (!dictionary.GetString("document", &document_body)) {
+ DLOG(ERROR) << "Response from Google Wallet missing document body";
+ return scoped_ptr<LegalDocument>();
+ }
+
+ return scoped_ptr<LegalDocument>(new LegalDocument(document_id,
+ display_name,
+ document_body));
+}
+
+bool WalletItems::LegalDocument::operator==(const LegalDocument& other) const {
+ return document_id_ == other.document_id_ &&
+ display_name_ == other.display_name_ &&
+ document_body_ == other.document_body_;
+}
+
+bool WalletItems::LegalDocument::operator!=(const LegalDocument& other) const {
+ return !(*this == other);
+}
+
+WalletItems::WalletItems(const std::vector<std::string>& required_actions,
+ const std::string& google_transaction_id,
+ const std::string& default_instrument_id,
+ const std::string& default_address_id)
+ : required_actions_(required_actions),
+ google_transaction_id_(google_transaction_id),
+ default_instrument_id_(default_instrument_id),
+ default_address_id_(default_address_id) {}
+
+WalletItems::~WalletItems() {}
+
+scoped_ptr<WalletItems>
+ WalletItems::CreateWalletItems(const base::DictionaryValue& dictionary) {
+ std::string google_transaction_id;
+ if (!dictionary.GetString("google_transaction_id", &google_transaction_id)) {
+ DLOG(ERROR) << "Response from Google wallet missing google transaction id";
+ return scoped_ptr<WalletItems>();
+ }
+
+ std::vector<std::string> required_action;
+ const ListValue* required_action_list;
+ if (dictionary.GetList("required_action", &required_action_list)) {
+ for (size_t i = 0; i < required_action_list->GetSize(); ++i) {
+ std::string action;
+ if (required_action_list->GetString(i, &action))
+ required_action.push_back(action);
+ }
+ } else {
+ DVLOG(1) << "Response from Google wallet missing required actions";
+ }
+
+ std::string default_instrument_id;
+ if (!dictionary.GetString("default_instrument_id", &default_instrument_id))
+ DVLOG(1) << "Response from Google wallet missing default instrument id";
+
+ std::string default_address_id;
+ if (!dictionary.GetString("default_address_id", &default_address_id))
+ DVLOG(1) << "Response from Google wallet missing default_address_id";
+
+ scoped_ptr<WalletItems> wallet_items(new WalletItems(required_action,
+ google_transaction_id,
+ default_instrument_id,
+ default_address_id));
+
+ const ListValue* legal_docs;
+ if (dictionary.GetList("required_legal_document", &legal_docs)) {
+ for (size_t i = 0; i < legal_docs->GetSize(); ++i) {
+ const DictionaryValue* legal_doc_dict;
+ if (legal_docs->GetDictionary(i, &legal_doc_dict)) {
+ scoped_ptr<LegalDocument> legal_doc(
+ LegalDocument::CreateLegalDocument(*legal_doc_dict));
+ if (legal_doc.get()) {
+ wallet_items->AddLegalDocument(legal_doc.Pass());
+ } else {
+ DLOG(ERROR) << "Malformed legal document in response from "
+ "Google wallet";
+ return scoped_ptr<WalletItems>();
+ }
+ }
+ }
+ } else {
+ DVLOG(1) << "Response from Google wallet missing legal docs";
+ }
+
+ const ListValue* instruments;
+ if (dictionary.GetList("instrument", &instruments)) {
+ for (size_t i = 0; i < instruments->GetSize(); ++i) {
+ const DictionaryValue* instrument_dict;
+ if (instruments->GetDictionary(i, &instrument_dict)) {
+ scoped_ptr<MaskedInstrument> instrument(
+ MaskedInstrument::CreateMaskedInstrument(*instrument_dict));
+ if (instrument.get())
+ wallet_items->AddInstrument(instrument.Pass());
+ else
+ DLOG(ERROR) << "Malformed instrument in response from Google Wallet";
+ }
+ }
+ } else {
+ DVLOG(1) << "Response from Google wallet missing instruments";
+ }
+
+ const ListValue* addresses;
+ if (dictionary.GetList("address", &addresses)) {
+ for (size_t i = 0; i < addresses->GetSize(); ++i) {
+ const DictionaryValue* address_dict;
+ if (addresses->GetDictionary(i, &address_dict)) {
+ scoped_ptr<Address> address(
+ Address::CreateAddressWithID(*address_dict));
+ if (address.get())
+ wallet_items->AddAddress(address.Pass());
+ else
+ DLOG(ERROR) << "Malformed address in response from Google Wallet";
+ }
+ }
+ } else {
+ DVLOG(1) << "Response from Google wallet missing addresses";
+ }
+
+ return wallet_items.Pass();
+}
+
+bool WalletItems::operator==(const WalletItems& other) const {
+ // TODO(ahutter): Check scoped vector equality.
+ return google_transaction_id_ == other.google_transaction_id_ &&
+ default_instrument_id_ == other.default_instrument_id_ &&
+ default_address_id_ == other.default_address_id_ &&
+ required_actions_ == required_actions_;
+}
+
+bool WalletItems::operator!=(const WalletItems& other) const {
+ return !(*this == other);
+}
+
+} // namespace wallet
+
diff --git a/chrome/browser/autofill/wallet/wallet_items.h b/chrome/browser/autofill/wallet/wallet_items.h
new file mode 100644
index 0000000..1963c66
--- /dev/null
+++ b/chrome/browser/autofill/wallet/wallet_items.h
@@ -0,0 +1,207 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_AUTOFILL_WALLET_WALLET_ITEMS_H_
+#define CHROME_BROWSER_AUTOFILL_WALLET_WALLET_ITEMS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "chrome/browser/autofill/wallet/wallet_address.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace wallet {
+
+class WalletItemsTest;
+
+// WalletItems primarily serves as a container for the user's instruments and
+// address, however, it also provides a transaction id which must be used
+// throughout all API calls being made using this data. Additionally, user
+// actions may be required before a purchase can be completed using Online
+// Wallet and those actions are present in the object as well.
+class WalletItems {
+ public:
+ // Container for all information about a credit card except for it's card
+ // verfication number (CVN) and it's complete primary account number (PAN).
+ class MaskedInstrument {
+ public:
+ enum Type {
+ AMEX,
+ DISCOVER,
+ MAESTRO,
+ MASTER_CARD,
+ SOLO,
+ SWITCH,
+ UNKNOWN, // Catch all type.
+ VISA,
+ };
+ enum Status {
+ BILLING_INCOMPLETE,
+ DECLINED,
+ EXPIRED,
+ INAPPLICABLE, // Catch all status.
+ PENDING,
+ UNSUPPORTED_COUNTRY,
+ VALID,
+ };
+
+ ~MaskedInstrument();
+
+ // Returns an empty scoped_ptr if input is invalid or a valid masked
+ // instrument.
+ static scoped_ptr<MaskedInstrument>
+ CreateMaskedInstrument(const base::DictionaryValue& dictionary);
+
+ bool operator==(const MaskedInstrument& other) const;
+ bool operator!=(const MaskedInstrument& other) const;
+
+ const std::string& descriptive_name() const { return descriptive_name_; }
+ const Type& type() const { return type_; }
+ const std::vector<std::string>& supported_currencies() const {
+ return supported_currencies_;
+ }
+ const std::string& last_four_digits() const { return last_four_digits_; }
+ int expiration_month() const { return expiration_month_; }
+ int expiration_year() const { return expiration_year_; }
+ const std::string& brand() const { return brand_; }
+ const Address& address() const { return *address_; }
+ const Status& status() const { return status_; }
+ const std::string& object_id() const { return object_id_; }
+
+ private:
+ friend class WalletItemsTest;
+ FRIEND_TEST_ALL_PREFIXES(WalletItemsTest, CreateMaskedInstrument);
+ FRIEND_TEST_ALL_PREFIXES(WalletItemsTest, CreateWalletItems);
+ MaskedInstrument(const std::string& descriptve_name,
+ const Type& type,
+ const std::vector<std::string>& supported_currencies,
+ const std::string& last_four_digits,
+ int expiration_month,
+ int expiration_year,
+ const std::string& brand,
+ scoped_ptr<Address> address,
+ const Status& status,
+ const std::string& object_id);
+ std::string descriptive_name_;
+ Type type_;
+ std::vector<std::string> supported_currencies_;
+ std::string last_four_digits_;
+ int expiration_month_;
+ int expiration_year_;
+ std::string brand_;
+ scoped_ptr<Address> address_;
+ Status status_;
+ std::string object_id_;
+ DISALLOW_COPY_AND_ASSIGN(MaskedInstrument);
+ };
+
+ // Class representing a legal document that the user must accept before they
+ // can use Online Wallet.
+ class LegalDocument {
+ public:
+ ~LegalDocument();
+
+ // Returns null if input is invalid or a valid legal document. Caller owns
+ // returned pointer.
+ static scoped_ptr<LegalDocument>
+ CreateLegalDocument(const base::DictionaryValue& dictionary);
+
+ bool operator==(const LegalDocument& other) const;
+ bool operator!=(const LegalDocument& other) const;
+
+ const std::string& document_id() const { return document_id_; }
+ const std::string& display_name() const { return display_name_; }
+ const std::string& document_body() const { return document_body_; }
+
+ private:
+ friend class WalletItemsTest;
+ FRIEND_TEST_ALL_PREFIXES(WalletItemsTest, CreateLegalDocument);
+ FRIEND_TEST_ALL_PREFIXES(WalletItemsTest, CreateWalletItems);
+ LegalDocument(const std::string& document_id,
+ const std::string& display_name,
+ const std::string& document_body);
+ std::string document_id_;
+ std::string display_name_;
+ std::string document_body_;
+ DISALLOW_COPY_AND_ASSIGN(LegalDocument);
+ };
+
+ ~WalletItems();
+
+ // Returns null on invalid input, an empty wallet items with required
+ // actions if any are present, and a populated wallet items otherwise. Caller
+ // owns returned pointer.
+ static scoped_ptr<WalletItems>
+ CreateWalletItems(const base::DictionaryValue& dictionary);
+
+ bool operator==(const WalletItems& other) const;
+ bool operator!=(const WalletItems& other) const;
+
+ void AddInstrument(scoped_ptr<MaskedInstrument> instrument) {
+ DCHECK(instrument.get());
+ instruments_.push_back(instrument.release());
+ }
+ void AddAddress(scoped_ptr<Address> address) {
+ DCHECK(address.get());
+ addresses_.push_back(address.release());
+ }
+ void AddLegalDocument(scoped_ptr<LegalDocument> legal_document) {
+ DCHECK(legal_document.get());
+ legal_documents_.push_back(legal_document.release());
+ }
+ const std::vector<std::string>& required_actions() const {
+ return required_actions_;
+ }
+ const std::string& google_transaction_id() const {
+ return google_transaction_id_;
+ }
+ const std::vector<MaskedInstrument*>& instruments() const {
+ return instruments_.get();
+ }
+ const std::string& default_instrument_id() const {
+ return default_instrument_id_;
+ }
+ const std::vector<Address*>& addresses() const { return addresses_.get(); }
+ const std::string& default_address_id() const {
+ return default_address_id_;
+ }
+ const std::vector<LegalDocument*>& legal_documents() const {
+ return legal_documents_.get();
+ }
+
+ private:
+ friend class WalletItemsTest;
+ FRIEND_TEST_ALL_PREFIXES(WalletItemsTest, CreateWalletItems);
+ FRIEND_TEST_ALL_PREFIXES(WalletItemsTest,
+ CreateWalletItemsWithRequiredActions);
+ WalletItems(const std::vector<std::string>& required_actions,
+ const std::string& google_transaction_id,
+ const std::string& default_instrument_id,
+ const std::string& default_address_id);
+ // Actions that must be completed by the user before a FullWallet can be
+ // issued to them by the Online Wallet service.
+ // TODO(ahutter): |required_actions_| should be members of an enum not
+ // strings. See http://crbug.com/165195.
+ std::vector<std::string> required_actions_;
+ std::string google_transaction_id_;
+ std::string default_instrument_id_;
+ std::string default_address_id_;
+ ScopedVector<MaskedInstrument> instruments_;
+ ScopedVector<Address> addresses_;
+ ScopedVector<LegalDocument> legal_documents_;
+ DISALLOW_COPY_AND_ASSIGN(WalletItems);
+};
+
+} // namespace wallet
+
+#endif // CHROME_BROWSER_AUTOFILL_WALLET_WALLET_ITEMS_H_
+
diff --git a/chrome/browser/autofill/wallet/wallet_items_unittest.cc b/chrome/browser/autofill/wallet/wallet_items_unittest.cc
new file mode 100644
index 0000000..2c6747f
--- /dev/null
+++ b/chrome/browser/autofill/wallet/wallet_items_unittest.cc
@@ -0,0 +1,447 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "chrome/browser/autofill/wallet/wallet_items.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char kMaskedInstrument[] =
+ "{"
+ " \"descriptive_name\":\"descriptive_name\","
+ " \"type\":\"VISA\","
+ " \"supported_currency\":"
+ " ["
+ " \"currency\""
+ " ],"
+ " \"last_four_digits\":\"last_four_digits\","
+ " \"expiration_month\":12,"
+ " \"expiration_year\":2012,"
+ " \"brand\":\"brand\","
+ " \"billing_address\":"
+ " {"
+ " \"name\":\"name\","
+ " \"address1\":\"address1\","
+ " \"address2\":\"address2\","
+ " \"city\":\"city\","
+ " \"state\":\"state\","
+ " \"postal_code\":\"postal_code\","
+ " \"phone_number\":\"phone_number\","
+ " \"country_code\":\"country_code\""
+ " },"
+ " \"status\":\"VALID\","
+ " \"object_id\":\"object_id\""
+ "}";
+
+const char kMaskedInstrumentMissingStatus[] =
+ "{"
+ " \"descriptive_name\":\"descriptive_name\","
+ " \"type\":\"VISA\","
+ " \"supported_currency\":"
+ " ["
+ " \"currency\""
+ " ],"
+ " \"last_four_digits\":\"last_four_digits\","
+ " \"expiration_month\":12,"
+ " \"expiration_year\":2012,"
+ " \"brand\":\"brand\","
+ " \"billing_address\":"
+ " {"
+ " \"name\":\"name\","
+ " \"address1\":\"address1\","
+ " \"address2\":\"address2\","
+ " \"city\":\"city\","
+ " \"state\":\"state\","
+ " \"postal_code\":\"postal_code\","
+ " \"phone_number\":\"phone_number\","
+ " \"country_code\":\"country_code\""
+ " },"
+ " \"object_id\":\"object_id\""
+ "}";
+
+const char kMaskedInstrumentMissingType[] =
+ "{"
+ " \"descriptive_name\":\"descriptive_name\","
+ " \"supported_currency\":"
+ " ["
+ " \"currency\""
+ " ],"
+ " \"last_four_digits\":\"last_four_digits\","
+ " \"expiration_month\":12,"
+ " \"expiration_year\":2012,"
+ " \"brand\":\"brand\","
+ " \"billing_address\":"
+ " {"
+ " \"name\":\"name\","
+ " \"address1\":\"address1\","
+ " \"address2\":\"address2\","
+ " \"city\":\"city\","
+ " \"state\":\"state\","
+ " \"postal_code\":\"postal_code\","
+ " \"phone_number\":\"phone_number\","
+ " \"country_code\":\"country_code\""
+ " },"
+ " \"status\":\"VALID\","
+ " \"object_id\":\"object_id\""
+ "}";
+
+const char kMaskedInstrumentMissingLastFourDigits[] =
+ "{"
+ " \"descriptive_name\":\"descriptive_name\","
+ " \"type\":\"VISA\","
+ " \"supported_currency\":"
+ " ["
+ " \"currency\""
+ " ],"
+ " \"expiration_month\":12,"
+ " \"expiration_year\":2012,"
+ " \"brand\":\"brand\","
+ " \"billing_address\":"
+ " {"
+ " \"name\":\"name\","
+ " \"address1\":\"address1\","
+ " \"address2\":\"address2\","
+ " \"city\":\"city\","
+ " \"state\":\"state\","
+ " \"postal_code\":\"postal_code\","
+ " \"phone_number\":\"phone_number\","
+ " \"country_code\":\"country_code\""
+ " },"
+ " \"status\":\"VALID\","
+ " \"object_id\":\"object_id\""
+ "}";
+
+const char kMaskedInstrumentMissingAddress[] =
+ "{"
+ " \"descriptive_name\":\"descriptive_name\","
+ " \"type\":\"VISA\","
+ " \"supported_currency\":"
+ " ["
+ " \"currency\""
+ " ],"
+ " \"last_four_digits\":\"last_four_digits\","
+ " \"expiration_month\":12,"
+ " \"expiration_year\":2012,"
+ " \"brand\":\"brand\","
+ " \"status\":\"VALID\","
+ " \"object_id\":\"object_id\""
+ "}";
+
+const char kMaskedInstrumentMalformedAddress[] =
+ "{"
+ " \"descriptive_name\":\"descriptive_name\","
+ " \"type\":\"VISA\","
+ " \"supported_currency\":"
+ " ["
+ " \"currency\""
+ " ],"
+ " \"last_four_digits\":\"last_four_digits\","
+ " \"expiration_month\":12,"
+ " \"expiration_year\":2012,"
+ " \"brand\":\"brand\","
+ " \"billing_address\":"
+ " {"
+ " \"address1\":\"address1\","
+ " \"address2\":\"address2\","
+ " \"city\":\"city\","
+ " \"state\":\"state\","
+ " \"phone_number\":\"phone_number\","
+ " \"country_code\":\"country_code\""
+ " },"
+ " \"status\":\"VALID\","
+ " \"object_id\":\"object_id\""
+ "}";
+
+const char kMaskedInstrumentMissingObjectId[] =
+ "{"
+ " \"descriptive_name\":\"descriptive_name\","
+ " \"type\":\"VISA\","
+ " \"supported_currency\":"
+ " ["
+ " \"currency\""
+ " ],"
+ " \"last_four_digits\":\"last_four_digits\","
+ " \"expiration_month\":12,"
+ " \"expiration_year\":2012,"
+ " \"brand\":\"brand\","
+ " \"billing_address\":"
+ " {"
+ " \"name\":\"name\","
+ " \"address1\":\"address1\","
+ " \"address2\":\"address2\","
+ " \"city\":\"city\","
+ " \"state\":\"state\","
+ " \"postal_code\":\"postal_code\","
+ " \"phone_number\":\"phone_number\","
+ " \"country_code\":\"country_code\""
+ " },"
+ " \"status\":\"VALID\""
+ "}";
+
+const char kLegalDocument[] =
+ "{"
+ " \"legal_document_id\":\"doc_id\","
+ " \"display_name\":\"display_name\","
+ " \"document\":\"doc_body\""
+ "}";
+
+const char kLegalDocumentMissingDocumentId[] =
+ "{"
+ " \"display_name\":\"display_name\","
+ " \"document\":\"doc_body\""
+ "}";
+
+const char kLegalDocumentMissingDisplayName[] =
+ "{"
+ " \"legal_document_id\":\"doc_id\","
+ " \"document\":\"doc_body\""
+ "}";
+
+const char kLegalDocumentMissingDocumentBody[] =
+ "{"
+ " \"legal_document_id\":\"doc_id\","
+ " \"display_name\":\"display_name\""
+ "}";
+
+const char kWalletItemsWithRequiredActions[] =
+ "{"
+ " \"google_transaction_id\":\"google_transaction_id\","
+ " \"required_action\":"
+ " ["
+ " \"required_action\""
+ " ]"
+ "}";
+
+const char kWalletItems[] =
+ "{"
+ " \"required_action\":"
+ " ["
+ " ],"
+ " \"google_transaction_id\":\"google_transaction_id\","
+ " \"instrument\":"
+ " ["
+ " {"
+ " \"descriptive_name\":\"descriptive_name\","
+ " \"type\":\"VISA\","
+ " \"supported_currency\":"
+ " ["
+ " \"currency\""
+ " ],"
+ " \"last_four_digits\":\"last_four_digits\","
+ " \"expiration_month\":12,"
+ " \"expiration_year\":2012,"
+ " \"brand\":\"brand\","
+ " \"billing_address\":"
+ " {"
+ " \"name\":\"name\","
+ " \"address1\":\"address1\","
+ " \"address2\":\"address2\","
+ " \"city\":\"city\","
+ " \"state\":\"state\","
+ " \"postal_code\":\"postal_code\","
+ " \"phone_number\":\"phone_number\","
+ " \"country_code\":\"country_code\""
+ " },"
+ " \"status\":\"VALID\","
+ " \"object_id\":\"object_id\""
+ " }"
+ " ],"
+ " \"default_instrument_id\":\"default_instrument_id\","
+ " \"address\":"
+ " ["
+ " {"
+ " \"id\":\"id\","
+ " \"phone_number\":\"phone_number\","
+ " \"postal_address\":"
+ " {"
+ " \"recipient_name\":\"recipient_name\","
+ " \"address_line_1\":\"address_line_1\","
+ " \"address_line_2\":\"address_line_2\","
+ " \"locality_name\":\"locality_name\","
+ " \"administrative_area_name\":\"administrative_area_name\","
+ " \"postal_code_number\":\"postal_code_number\","
+ " \"country_name_code\":\"country_name_code\""
+ " }"
+ " }"
+ " ],"
+ " \"default_address_id\":\"default_address_id\","
+ " \"required_legal_document\":"
+ " ["
+ " {"
+ " \"legal_document_id\":\"doc_id\","
+ " \"display_name\":\"display_name\","
+ " \"document\":\"doc_body\""
+ " }"
+ " ]"
+ "}";
+
+} // anonymous namespace
+
+namespace wallet {
+
+class WalletItemsTest : public testing::Test {
+ public:
+ WalletItemsTest() {}
+ protected:
+ void SetUpDictionary(const std::string& json) {
+ scoped_ptr<Value> value(base::JSONReader::Read(json));
+ DCHECK(value.get());
+ DCHECK(value->IsType(Value::TYPE_DICTIONARY));
+ dict.reset(static_cast<DictionaryValue*>(value.release()));
+ }
+ scoped_ptr<DictionaryValue> dict;
+};
+
+TEST_F(WalletItemsTest, CreateMaskedInstrumentMissingStatus) {
+ SetUpDictionary(kMaskedInstrumentMissingStatus);
+ ASSERT_EQ(NULL,
+ WalletItems::MaskedInstrument::CreateMaskedInstrument(*dict).get());
+}
+
+TEST_F(WalletItemsTest, CreateMaskedInstrumentMissingType) {
+ SetUpDictionary(kMaskedInstrumentMissingType);
+ ASSERT_EQ(NULL,
+ WalletItems::MaskedInstrument::CreateMaskedInstrument(*dict).get());
+}
+
+TEST_F(WalletItemsTest, CreateMaskedInstrumentMissingLastFourDigits) {
+ SetUpDictionary(kMaskedInstrumentMissingLastFourDigits);
+ ASSERT_EQ(NULL,
+ WalletItems::MaskedInstrument::CreateMaskedInstrument(*dict).get());
+}
+
+TEST_F(WalletItemsTest, CreateMaskedInstrumentMissingAddress) {
+ SetUpDictionary(kMaskedInstrumentMissingAddress);
+ ASSERT_EQ(NULL,
+ WalletItems::MaskedInstrument::CreateMaskedInstrument(*dict).get());
+}
+
+TEST_F(WalletItemsTest, CreateMaskedInstrumentMalformedAddress) {
+ SetUpDictionary(kMaskedInstrumentMalformedAddress);
+ ASSERT_EQ(NULL,
+ WalletItems::MaskedInstrument::CreateMaskedInstrument(*dict).get());
+}
+
+TEST_F(WalletItemsTest, CreateMaskedInstrumentMissingObjectId) {
+ SetUpDictionary(kMaskedInstrumentMissingObjectId);
+ ASSERT_EQ(NULL,
+ WalletItems::MaskedInstrument::CreateMaskedInstrument(*dict).get());
+}
+
+TEST_F(WalletItemsTest, CreateMaskedInstrument) {
+ SetUpDictionary(kMaskedInstrument);
+ scoped_ptr<Address> address(new Address("country_code",
+ "name",
+ "address1",
+ "address2",
+ "city",
+ "state",
+ "postal_code",
+ "phone_number",
+ ""));
+ std::vector<std::string> supported_currencies;
+ supported_currencies.push_back("currency");
+ WalletItems::MaskedInstrument masked_instrument(
+ "descriptive_name",
+ WalletItems::MaskedInstrument::VISA,
+ supported_currencies,
+ "last_four_digits",
+ 12,
+ 2012,
+ "brand",
+ address.Pass(),
+ WalletItems::MaskedInstrument::VALID,
+ "object_id");
+ ASSERT_EQ(masked_instrument,
+ *WalletItems::MaskedInstrument::CreateMaskedInstrument(*dict));
+}
+
+TEST_F(WalletItemsTest, CreateLegalDocumentMissingDocId) {
+ SetUpDictionary(kLegalDocumentMissingDocumentId);
+ ASSERT_EQ(NULL, WalletItems::LegalDocument::CreateLegalDocument(*dict).get());
+}
+
+TEST_F(WalletItemsTest, CreateLegalDocumentMissingDisplayName) {
+ SetUpDictionary(kLegalDocumentMissingDisplayName);
+ ASSERT_EQ(NULL, WalletItems::LegalDocument::CreateLegalDocument(*dict).get());
+}
+
+TEST_F(WalletItemsTest, CreateLegalDocumentMissingDocBody) {
+ SetUpDictionary(kLegalDocumentMissingDocumentBody);
+ ASSERT_EQ(NULL, WalletItems::LegalDocument::CreateLegalDocument(*dict).get());
+}
+
+TEST_F(WalletItemsTest, CreateLegalDocument) {
+ SetUpDictionary(kLegalDocument);
+ WalletItems::LegalDocument expected("doc_id", "display_name", "doc_body");
+ ASSERT_EQ(expected,
+ *WalletItems::LegalDocument::CreateLegalDocument(*dict));
+}
+
+TEST_F(WalletItemsTest, CreateWalletItemsWithRequiredActions) {
+ SetUpDictionary(kWalletItemsWithRequiredActions);
+ std::vector<std::string> required_actions;
+ required_actions.push_back("required_action");
+ WalletItems expected(required_actions, "google_transaction_id", "", "");
+ ASSERT_EQ(expected, *WalletItems::CreateWalletItems(*dict));
+}
+
+TEST_F(WalletItemsTest, CreateWalletItems) {
+ SetUpDictionary(kWalletItems);
+ std::vector<std::string> required_actions;
+ WalletItems expected(required_actions,
+ "google_transaction_id",
+ "default_instrument_id",
+ "default_address_id");
+
+ scoped_ptr<Address> billing_address(new Address("country_code",
+ "name",
+ "address1",
+ "address2",
+ "city",
+ "state",
+ "postal_code",
+ "phone_number",
+ ""));
+ std::vector<std::string> supported_currencies;
+ supported_currencies.push_back("currency");
+ scoped_ptr<WalletItems::MaskedInstrument> masked_instrument(
+ new WalletItems::MaskedInstrument("descriptive_name",
+ WalletItems::MaskedInstrument::VISA,
+ supported_currencies,
+ "last_four_digits",
+ 12,
+ 2012,
+ "brand",
+ billing_address.Pass(),
+ WalletItems::MaskedInstrument::VALID,
+ "object_id"));
+ expected.AddInstrument(masked_instrument.Pass());
+
+ scoped_ptr<Address> shipping_address(new Address("country_code",
+ "name",
+ "address1",
+ "address2",
+ "city",
+ "state",
+ "postal_code",
+ "phone_number",
+ "id"));
+ expected.AddAddress(shipping_address.Pass());
+
+ scoped_ptr<WalletItems::LegalDocument> legal_document(
+ new WalletItems::LegalDocument("doc_id",
+ "display_name",
+ "doc_body"));
+ expected.AddLegalDocument(legal_document.Pass());
+
+ ASSERT_EQ(expected, *WalletItems::CreateWalletItems(*dict));
+}
+
+} // namespace wallet
+
diff --git a/chrome/browser/autofill/wallet/wallet_service_url.cc b/chrome/browser/autofill/wallet/wallet_service_url.cc
new file mode 100644
index 0000000..f94d90d
--- /dev/null
+++ b/chrome/browser/autofill/wallet/wallet_service_url.cc
@@ -0,0 +1,62 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/autofill/wallet/wallet_service_url.h"
+
+#include <string>
+
+#include "base/command_line.h"
+#include "chrome/common/chrome_switches.h"
+#include "googleurl/src/gurl.h"
+
+namespace {
+
+const char kDefaultWalletServiceUrl[] =
+ "https://wallet.google.com/online/v2/wallet/autocheckout/";
+
+const char kDefaultWalletSecureServiceUrl[] =
+ "https://wallet.google.com/online-secure/temporarydata/cvv?s7e=cvv";
+
+GURL GetBaseWalletUrl() {
+ const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+ std::string base_wallet_service_url =
+ command_line.GetSwitchValueASCII(switches::kWalletServiceUrl);
+ return !base_wallet_service_url.empty() ? GURL(base_wallet_service_url) :
+ GURL(kDefaultWalletServiceUrl);
+}
+
+} // anonymous namespace
+
+// TODO(ahutter): This shouldn't live in this class. See
+// http://crbug.com/164281.
+const char wallet::kApiKey[] = "abcdefg";
+
+GURL wallet::GetGetWalletItemsUrl() {
+ return GetBaseWalletUrl().Resolve("getWalletItemsJwtless");
+}
+
+GURL wallet::GetGetFullWalletUrl() {
+ return GetBaseWalletUrl().Resolve("getFullWalletJwtless");
+}
+
+GURL wallet::GetAcceptLegalDocumentsUrl() {
+ return GetBaseWalletUrl().Resolve("acceptLegalDocuments");
+}
+
+GURL wallet::GetSendStatusUrl() {
+ return GetBaseWalletUrl().Resolve("reportStatus");
+}
+
+GURL wallet::GetSaveToWalletUrl() {
+ return GetBaseWalletUrl().Resolve("saveToWallet");
+}
+
+GURL wallet::GetSecureUrl() {
+ const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+ std::string wallet_secure_url =
+ command_line.GetSwitchValueASCII(switches::kWalletSecureServiceUrl);
+ return !wallet_secure_url.empty() ? GURL(wallet_secure_url) :
+ GURL(kDefaultWalletSecureServiceUrl);
+}
+
diff --git a/chrome/browser/autofill/wallet/wallet_service_url.h b/chrome/browser/autofill/wallet/wallet_service_url.h
new file mode 100644
index 0000000..e33bae8
--- /dev/null
+++ b/chrome/browser/autofill/wallet/wallet_service_url.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_AUTOFILL_WALLET_WALLET_SERVICE_URL_H_
+#define CHROME_BROWSER_AUTOFILL_WALLET_WALLET_SERVICE_URL_H_
+
+class GURL;
+
+namespace wallet {
+
+extern const char kApiKey[];
+GURL GetGetWalletItemsUrl();
+GURL GetGetFullWalletUrl();
+GURL GetAcceptLegalDocumentsUrl();
+GURL GetSecureUrl();
+GURL GetSendStatusUrl();
+GURL GetSaveToWalletUrl();
+
+} // namespace wallet
+
+#endif // CHROME_BROWSER_AUTOFILL_WALLET_WALLET_SERVICE_URL_H_
+
diff --git a/chrome/browser/autofill/wallet/wallet_service_url_unittest.cc b/chrome/browser/autofill/wallet/wallet_service_url_unittest.cc
new file mode 100644
index 0000000..9ac1d2a
--- /dev/null
+++ b/chrome/browser/autofill/wallet/wallet_service_url_unittest.cc
@@ -0,0 +1,32 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/autofill/wallet/wallet_service_url.h"
+#include "googleurl/src/gurl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace wallet {
+
+TEST(WalletServiceUrl, CheckDefaultUrls) {
+ ASSERT_EQ("https://wallet.google.com/online/v2/wallet/autocheckout/"
+ "getWalletItemsJwtless",
+ GetGetWalletItemsUrl().spec());
+ ASSERT_EQ("https://wallet.google.com/online/v2/wallet/autocheckout/"
+ "getFullWalletJwtless",
+ GetGetFullWalletUrl().spec());
+ ASSERT_EQ("https://wallet.google.com/online/v2/wallet/autocheckout/"
+ "acceptLegalDocuments",
+ GetAcceptLegalDocumentsUrl().spec());
+ ASSERT_EQ("https://wallet.google.com/online/v2/wallet/autocheckout/"
+ "reportStatus",
+ GetSendStatusUrl().spec());
+ ASSERT_EQ("https://wallet.google.com/online/v2/wallet/autocheckout/"
+ "saveToWallet",
+ GetSaveToWalletUrl().spec());
+ ASSERT_EQ("https://wallet.google.com/online-secure/temporarydata/cvv?s7e=cvv",
+ GetSecureUrl().spec());
+}
+
+} // namespace wallet
+