diff options
author | ahutter@chromium.org <ahutter@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-12-20 02:46:28 +0000 |
---|---|---|
committer | ahutter@chromium.org <ahutter@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-12-20 02:46:28 +0000 |
commit | ce63d6b29059447b36aa2fc52ea608b1e785692e (patch) | |
tree | db5a93bac2703504c6c8564c6ef58ec02abd8353 /chrome/browser/autofill | |
parent | 276c8b6cc32fe5e03ad779ff2c506a090b87cd4f (diff) | |
download | chromium_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')
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, ¤cy)) + 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 + |