diff options
Diffstat (limited to 'components/autofill/content/browser')
32 files changed, 8692 insertions, 0 deletions
diff --git a/components/autofill/content/browser/wallet/OWNERS b/components/autofill/content/browser/wallet/OWNERS new file mode 100644 index 0000000..cba869d --- /dev/null +++ b/components/autofill/content/browser/wallet/OWNERS @@ -0,0 +1 @@ +dbeam@chromium.org diff --git a/components/autofill/content/browser/wallet/encryption_escrow_client.cc b/components/autofill/content/browser/wallet/encryption_escrow_client.cc new file mode 100644 index 0000000..c0adf08 --- /dev/null +++ b/components/autofill/content/browser/wallet/encryption_escrow_client.cc @@ -0,0 +1,189 @@ +// Copyright 2013 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 "components/autofill/content/browser/wallet/encryption_escrow_client.h" + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/stringprintf.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/content/browser/wallet/encryption_escrow_client_observer.h" +#include "components/autofill/content/browser/wallet/instrument.h" +#include "components/autofill/content/browser/wallet/wallet_service_url.h" +#include "googleurl/src/gurl.h" +#include "net/base/escape.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 kEscrowInstrumentInformationFormat[] = "gid=%s&cardNumber=%s&cvv=%s"; +const char kEscrowCardVerficationNumberFormat[] = "gid=%s&cvv=%s"; +const char kApplicationMimeType[] = "application/x-www-form-urlencoded"; + +// The maximum number of bits in the one time pad that the server is willing to +// accept. +const size_t kMaxBits = 56; + +// The minimum number of bits in the one time pad that the server is willing to +// accept. +const size_t kMinBits = 40; + +} // anonymous namespace + +namespace autofill { +namespace wallet { + +EncryptionEscrowClient::EncryptionEscrowClient( + net::URLRequestContextGetter* context_getter, + EncryptionEscrowClientObserver* observer) + : context_getter_(context_getter), + observer_(observer), + request_type_(NO_PENDING_REQUEST) { + DCHECK(context_getter_.get()); + DCHECK(observer_); +} + +EncryptionEscrowClient::~EncryptionEscrowClient() {} + +void EncryptionEscrowClient::EncryptOneTimePad( + const std::vector<uint8>& one_time_pad) { + DCHECK_EQ(NO_PENDING_REQUEST, request_type_); + size_t num_bits = one_time_pad.size() * 8; + DCHECK_LE(num_bits, kMaxBits); + DCHECK_GE(num_bits, kMinBits); + + request_type_ = ENCRYPT_ONE_TIME_PAD; + + std::string post_body = base::StringPrintf( + kEncryptOtpBodyFormat, + base::HexEncode(&num_bits, 1).c_str(), + base::HexEncode(&(one_time_pad[0]), one_time_pad.size()).c_str()); + + MakeRequest(GetEncryptionUrl(), post_body); +} + +void EncryptionEscrowClient::EscrowInstrumentInformation( + const Instrument& new_instrument, + const std::string& obfuscated_gaia_id) { + DCHECK_EQ(NO_PENDING_REQUEST, request_type_); + request_type_ = ESCROW_INSTRUMENT_INFORMATION; + + const std::string& primary_account_number = + net::EscapeUrlEncodedData( + UTF16ToUTF8(new_instrument.primary_account_number()), true); + const std::string& card_verification_number = + net::EscapeUrlEncodedData( + UTF16ToUTF8(new_instrument.card_verification_number()), true); + + std::string post_body = base::StringPrintf( + kEscrowInstrumentInformationFormat, + obfuscated_gaia_id.c_str(), + primary_account_number.c_str(), + card_verification_number.c_str()); + + MakeRequest(GetEscrowUrl(), post_body); +} + +void EncryptionEscrowClient::EscrowCardVerificationNumber( + const std::string& card_verification_number, + const std::string& obfuscated_gaia_id) { + DCHECK_EQ(NO_PENDING_REQUEST, request_type_); + request_type_ = ESCROW_CARD_VERIFICATION_NUMBER; + + std::string post_body = base::StringPrintf( + kEscrowCardVerficationNumberFormat, + obfuscated_gaia_id.c_str(), + card_verification_number.c_str()); + + MakeRequest(GetEscrowUrl(), post_body); +} + +void EncryptionEscrowClient::CancelRequest() { + request_.reset(); + request_type_ = NO_PENDING_REQUEST; +} + +bool EncryptionEscrowClient::HasRequestInProgress() const { + return request_; +} + +void EncryptionEscrowClient::MakeRequest(const GURL& url, + const std::string& post_body) { + DCHECK(!request_.get()); + + request_.reset(net::URLFetcher::Create( + 1, url, net::URLFetcher::POST, this)); + request_->SetRequestContext(context_getter_.get()); + DVLOG(1) << "url=" << url << ", post_body=" << post_body; + request_->SetUploadData(kApplicationMimeType, post_body); + request_->Start(); + + observer_->OnDidMakeRequest(); +} + +// TODO(ahutter): Add manual retry logic if it's necessary. +void EncryptionEscrowClient::OnURLFetchComplete( + const net::URLFetcher* source) { + DCHECK(observer_); + scoped_ptr<net::URLFetcher> old_request = request_.Pass(); + DCHECK_EQ(source, old_request.get()); + + DVLOG(1) << "Got response from " << source->GetOriginalURL(); + + RequestType type = request_type_; + request_type_ = NO_PENDING_REQUEST; + + std::string data; + source->GetResponseAsString(&data); + DVLOG(1) << "Response body: " << data; + + if (source->GetResponseCode() != net::HTTP_OK) { + observer_->OnNetworkError(source->GetResponseCode()); + return; + } + + if (data.empty()) { + HandleMalformedResponse(old_request.get()); + return; + } + + switch (type) { + case ENCRYPT_ONE_TIME_PAD: { + std::vector<std::string> splits; + // The response from the server should be formatted as + // "<session material>|<encrypted one time pad>". + base::SplitString(data, '|', &splits); + if (splits.size() == 2) + observer_->OnDidEncryptOneTimePad(splits[1], splits[0]); + else + HandleMalformedResponse(old_request.get()); + break; + } + + case ESCROW_INSTRUMENT_INFORMATION: + observer_->OnDidEscrowInstrumentInformation(data); + break; + + case ESCROW_CARD_VERIFICATION_NUMBER: + observer_->OnDidEscrowCardVerificationNumber(data); + break; + + case NO_PENDING_REQUEST: + NOTREACHED(); + } +} + +void EncryptionEscrowClient::HandleMalformedResponse(net::URLFetcher* request) { + // Called to inform exponential backoff logic of the error. + request->ReceivedContentWasMalformed(); + observer_->OnMalformedResponse(); +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/content/browser/wallet/encryption_escrow_client.h b/components/autofill/content/browser/wallet/encryption_escrow_client.h new file mode 100644 index 0000000..456c972 --- /dev/null +++ b/components/autofill/content/browser/wallet/encryption_escrow_client.h @@ -0,0 +1,100 @@ +// Copyright 2013 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 COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_ENCRYPTION_ESCROW_CLIENT_H_ +#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_ENCRYPTION_ESCROW_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 URLFetcher; +class URLRequestContextGetter; +} + +namespace autofill { +namespace wallet { + +class EncryptionEscrowClientObserver; +class Instrument; + +// EncrytionEscrowClient is responsible for making calls to the Online Wallet +// encryption and escrow backend. +class EncryptionEscrowClient : public net::URLFetcherDelegate { + public: + // |observer| must outlive |this|. + EncryptionEscrowClient(net::URLRequestContextGetter* context_getter, + EncryptionEscrowClientObserver* observer); + virtual ~EncryptionEscrowClient(); + + // Sends |one_time_pad|, a vector of cryptographically secure random bytes, to + // Online Wallet to be encrypted. These bytes must be generated using + // crypto/random.h. + void EncryptOneTimePad(const std::vector<uint8>& one_time_pad); + + // Escrows the primary account number and card verfication number of + // |new_instrument| with Online Wallet. The escrow is keyed off of + // |obfuscated_gaia_id|. + void EscrowInstrumentInformation(const Instrument& new_instrument, + const std::string& obfuscated_gaia_id); + + // Escrows the card verfication number of an existing instrument with Online + // Wallet. The escrow is keyed off of |obfuscated_gaia_id|. + void EscrowCardVerificationNumber(const std::string& card_verification_number, + const std::string& obfuscated_gaia_id); + + // Cancels |request_| (if it exists). + void CancelRequest(); + + bool HasRequestInProgress() const; + + protected: + // Exposed for testing. + const net::URLFetcher* request() const { return request_.get(); } + + private: + enum RequestType { + NO_PENDING_REQUEST, + ENCRYPT_ONE_TIME_PAD, + ESCROW_INSTRUMENT_INFORMATION, + ESCROW_CARD_VERIFICATION_NUMBER, + }; + + // Posts |post_body| to |url|. When the request is complete, |observer_| is + // notified of the result. + void MakeRequest(const GURL& url, const std::string& post_body); + + // Performs bookkeeping tasks for any invalid requests. + void HandleMalformedResponse(net::URLFetcher* request); + + // net::URLFetcherDelegate: + 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. + EncryptionEscrowClientObserver* const 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(EncryptionEscrowClient); +}; + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_ENCRYPTION_ESCROW_CLIENT_H_ diff --git a/components/autofill/content/browser/wallet/encryption_escrow_client_observer.h b/components/autofill/content/browser/wallet/encryption_escrow_client_observer.h new file mode 100644 index 0000000..d5ff17d --- /dev/null +++ b/components/autofill/content/browser/wallet/encryption_escrow_client_observer.h @@ -0,0 +1,53 @@ +// Copyright 2013 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 COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_ENCRYPTION_ESCROW_CLIENT_OBSERVER_H_ +#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_ENCRYPTION_ESCROW_CLIENT_OBSERVER_H_ + +#include <string> + +namespace autofill { +namespace wallet { + +// EncryptionEscrowClientObserver is to be implemented by any classes making +// calls with EncryptionEscrowClient. The appropriate callback method will be +// called on EncryptionEscrowClientObserver with the response from the Online +// Wallet encryption and escrow backend. +class EncryptionEscrowClientObserver { + public: + // Called when an EncryptOneTimePad request finishes successfully. + // |encrypted_one_time_pad| and |session_material| must be used when getting a + // FullWallet. + virtual void OnDidEncryptOneTimePad(const std::string& encrypted_one_time_pad, + const std::string& session_material) = 0; + + // Called when an EscrowCardVerificationNumber request finishes + // successfully. |escrow_handle| must be used when authenticating an + // instrument. + virtual void OnDidEscrowCardVerificationNumber( + const std::string& escrow_handle) = 0; + + // Called when an EscrowInstrumentInformation request finishes successfully. + // |escrow_handle| must be used when saving a new instrument. + virtual void OnDidEscrowInstrumentInformation( + const std::string& escrow_handle) = 0; + + // Called when a request is made to the encryption escrow server. + virtual void OnDidMakeRequest() = 0; + + // Called when a request fails due to a network error or if the response was + // invalid. + virtual void OnNetworkError(int response_code) = 0; + + // Called when a request fails due to a malformed response. + virtual void OnMalformedResponse() = 0; + + protected: + virtual ~EncryptionEscrowClientObserver() {} +}; + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_ENCRYPTION_ESCROW_CLIENT_OBSERVER_H_ diff --git a/components/autofill/content/browser/wallet/encryption_escrow_client_unittest.cc b/components/autofill/content/browser/wallet/encryption_escrow_client_unittest.cc new file mode 100644 index 0000000..f042541 --- /dev/null +++ b/components/autofill/content/browser/wallet/encryption_escrow_client_unittest.cc @@ -0,0 +1,204 @@ +// Copyright 2013 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/memory/scoped_ptr.h" +#include "chrome/test/base/testing_profile.h" +#include "components/autofill/content/browser/wallet/encryption_escrow_client.h" +#include "components/autofill/content/browser/wallet/encryption_escrow_client_observer.h" +#include "components/autofill/content/browser/wallet/instrument.h" +#include "components/autofill/content/browser/wallet/wallet_test_util.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 autofill { +namespace wallet { + +namespace { + +const char kEncryptOtpRequest[] = "cvv=30:000102030405"; +const char kEncryptOtpResponse[] = "session_material|encrypted_one_time_pad"; +const char kEscrowInstrumentInformationRequest[] = + "gid=obfuscated_gaia_id&cardNumber=4444444444444448&cvv=123"; +const char kEscrowCardVerificationNumberRequest[] = + "gid=obfuscated_gaia_id&cvv=123"; + +class MockEncryptionEscrowClientObserver : + public EncryptionEscrowClientObserver, + public base::SupportsWeakPtr<MockEncryptionEscrowClientObserver> { + public: + MockEncryptionEscrowClientObserver() {} + ~MockEncryptionEscrowClientObserver() {} + + MOCK_METHOD2(OnDidEncryptOneTimePad, + void(const std::string& encrypted_one_time_pad, + const std::string& session_material)); + MOCK_METHOD1(OnDidEscrowCardVerificationNumber, + void(const std::string& escrow_handle)); + MOCK_METHOD1(OnDidEscrowInstrumentInformation, + void(const std::string& escrow_handle)); + MOCK_METHOD0(OnDidMakeRequest, void()); + MOCK_METHOD0(OnMalformedResponse, void()); + MOCK_METHOD1(OnNetworkError, void(int response_code)); +}; + +} // namespace + +class TestEncryptionEscrowClient : public EncryptionEscrowClient { + public: + TestEncryptionEscrowClient( + net::URLRequestContextGetter* context_getter, + EncryptionEscrowClientObserver* observer) + : EncryptionEscrowClient(context_getter, observer) {} + + bool HasRequestInProgress() const { + return request() != NULL; + } +}; + +class EncryptionEscrowClientTest : public testing::Test { + public: + EncryptionEscrowClientTest() + : instrument_(GetTestInstrument()), + io_thread_(content::BrowserThread::IO) {} + + virtual void SetUp() OVERRIDE { + io_thread_.StartIOThread(); + profile_.CreateRequestContext(); + encryption_escrow_client_.reset(new TestEncryptionEscrowClient( + profile_.GetRequestContext(), &observer_)); + } + + virtual void TearDown() OVERRIDE { + encryption_escrow_client_.reset(); + profile_.ResetRequestContext(); + io_thread_.Stop(); + } + + std::vector<uint8> MakeOneTimePad() { + std::vector<uint8> one_time_pad; + one_time_pad.push_back(0); + one_time_pad.push_back(1); + one_time_pad.push_back(2); + one_time_pad.push_back(3); + one_time_pad.push_back(4); + one_time_pad.push_back(5); + return one_time_pad; + } + + void VerifyAndFinishRequest(net::HttpStatusCode response_code, + const std::string& request_body, + const std::string& response_body) { + net::TestURLFetcher* fetcher = factory_.GetFetcherByID(1); + ASSERT_TRUE(fetcher); + EXPECT_EQ(request_body, fetcher->upload_data()); + fetcher->set_response_code(response_code); + fetcher->SetResponseString(response_body); + fetcher->delegate()->OnURLFetchComplete(fetcher); + } + + protected: + scoped_ptr<TestEncryptionEscrowClient> encryption_escrow_client_; + testing::StrictMock<MockEncryptionEscrowClientObserver> observer_; + scoped_ptr<Instrument> instrument_; + + + private: + // The profile's request context must be released on the IO thread. + content::TestBrowserThread io_thread_; + TestingProfile profile_; + net::TestURLFetcherFactory factory_; +}; + +TEST_F(EncryptionEscrowClientTest, NetworkError) { + EXPECT_CALL(observer_, OnDidMakeRequest()).Times(1); + EXPECT_CALL(observer_, OnNetworkError(net::HTTP_UNAUTHORIZED)).Times(1); + + encryption_escrow_client_->EscrowInstrumentInformation(*instrument_, + "obfuscated_gaia_id"); + VerifyAndFinishRequest(net::HTTP_UNAUTHORIZED, + kEscrowInstrumentInformationRequest, + std::string()); +} + +TEST_F(EncryptionEscrowClientTest, EscrowInstrumentInformationSuccess) { + EXPECT_CALL(observer_, OnDidMakeRequest()).Times(1); + EXPECT_CALL(observer_, OnDidEscrowInstrumentInformation("abc")).Times(1); + + encryption_escrow_client_->EscrowInstrumentInformation(*instrument_, + "obfuscated_gaia_id"); + VerifyAndFinishRequest(net::HTTP_OK, + kEscrowInstrumentInformationRequest, + "abc"); +} + +TEST_F(EncryptionEscrowClientTest, EscrowInstrumentInformationFailure) { + EXPECT_CALL(observer_, OnDidMakeRequest()).Times(1); + EXPECT_CALL(observer_, OnMalformedResponse()).Times(1); + + encryption_escrow_client_->EscrowInstrumentInformation(*instrument_, + "obfuscated_gaia_id"); + VerifyAndFinishRequest(net::HTTP_OK, + kEscrowInstrumentInformationRequest, + std::string()); +} + +TEST_F(EncryptionEscrowClientTest, EscrowCardVerificationNumberSuccess) { + EXPECT_CALL(observer_, OnDidMakeRequest()).Times(1); + EXPECT_CALL(observer_, OnDidEscrowCardVerificationNumber("abc")).Times(1); + + encryption_escrow_client_->EscrowCardVerificationNumber("123", + "obfuscated_gaia_id"); + VerifyAndFinishRequest(net::HTTP_OK, + kEscrowCardVerificationNumberRequest, + "abc"); +} + +TEST_F(EncryptionEscrowClientTest, EscrowCardVerificationNumberFailure) { + EXPECT_CALL(observer_, OnDidMakeRequest()).Times(1); + EXPECT_CALL(observer_, OnMalformedResponse()).Times(1); + + encryption_escrow_client_->EscrowCardVerificationNumber("123", + "obfuscated_gaia_id"); + VerifyAndFinishRequest(net::HTTP_OK, + kEscrowCardVerificationNumberRequest, + std::string()); +} + +TEST_F(EncryptionEscrowClientTest, EncryptOneTimePadSuccess) { + EXPECT_CALL(observer_, OnDidMakeRequest()).Times(1); + EXPECT_CALL(observer_, + OnDidEncryptOneTimePad("encrypted_one_time_pad", + "session_material")).Times(1); + + encryption_escrow_client_->EncryptOneTimePad(MakeOneTimePad()); + VerifyAndFinishRequest(net::HTTP_OK, kEncryptOtpRequest, kEncryptOtpResponse); +} + +TEST_F(EncryptionEscrowClientTest, EncryptOneTimePadFailure) { + EXPECT_CALL(observer_, OnDidMakeRequest()).Times(1); + EXPECT_CALL(observer_, OnMalformedResponse()).Times(1); + + encryption_escrow_client_->EncryptOneTimePad(MakeOneTimePad()); + VerifyAndFinishRequest(net::HTTP_OK, kEncryptOtpRequest, std::string()); +} + +TEST_F(EncryptionEscrowClientTest, CancelRequest) { + EXPECT_CALL(observer_, OnDidMakeRequest()).Times(1); + + encryption_escrow_client_->EncryptOneTimePad(MakeOneTimePad()); + EXPECT_TRUE(encryption_escrow_client_->HasRequestInProgress()); + + encryption_escrow_client_->CancelRequest(); + EXPECT_FALSE(encryption_escrow_client_->HasRequestInProgress()); +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/content/browser/wallet/full_wallet.cc b/components/autofill/content/browser/wallet/full_wallet.cc new file mode 100644 index 0000000..1605f22 --- /dev/null +++ b/components/autofill/content/browser/wallet/full_wallet.cc @@ -0,0 +1,283 @@ +// Copyright 2013 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 "components/autofill/content/browser/wallet/full_wallet.h" + +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "components/autofill/browser/credit_card.h" + +namespace { + +const size_t kPanSize = 16; +const size_t kBinSize = 6; +const size_t kCvnSize = 3; + +} // anonymous namespace + +namespace autofill { +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<RequiredAction>& 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<RequiredAction> required_actions; + if (dictionary.GetList("required_action", &required_actions_list)) { + for (size_t i = 0; i < required_actions_list->GetSize(); ++i) { + std::string action_string; + if (required_actions_list->GetString(i, &action_string)) { + RequiredAction action = ParseRequiredActionFromString(action_string); + if (!ActionAppliesToFullWallet(action)) { + DLOG(ERROR) << "Response from Google wallet with bad required action:" + " \"" << action_string << "\""; + return scoped_ptr<FullWallet>(); + } + required_actions.push_back(action); + } + } + if (required_actions.size() > 0) { + return scoped_ptr<FullWallet>(new FullWallet(-1, + -1, + std::string(), + std::string(), + 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::CreateAddress(*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)); +} + +base::string16 FullWallet::GetInfo(AutofillFieldType type) { + switch (type) { + case CREDIT_CARD_NUMBER: + return UTF8ToUTF16(GetPan()); + + case CREDIT_CARD_NAME: + return billing_address()->recipient_name(); + + case CREDIT_CARD_VERIFICATION_CODE: + return UTF8ToUTF16(GetCvn()); + + case CREDIT_CARD_EXP_MONTH: + if (expiration_month() == 0) + return base::string16(); + return base::IntToString16(expiration_month()); + + case CREDIT_CARD_EXP_4_DIGIT_YEAR: + if (expiration_year() == 0) + return base::string16(); + return base::IntToString16(expiration_year()); + + case CREDIT_CARD_EXP_2_DIGIT_YEAR: + if (expiration_year() == 0) + return base::string16(); + return base::IntToString16(expiration_year() % 100); + + case CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR: + if (expiration_month() == 0 || expiration_year() == 0) + return base::string16(); + return base::IntToString16(expiration_month()) + ASCIIToUTF16("/") + + base::IntToString16(expiration_year() % 100); + + case CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR: + if (expiration_month() == 0 || expiration_year() == 0) + return base::string16(); + return base::IntToString16(expiration_month()) + ASCIIToUTF16("/") + + base::IntToString16(expiration_year()); + + case CREDIT_CARD_TYPE: { + std::string internal_type = + CreditCard::GetCreditCardType(UTF8ToUTF16(GetPan())); + if (internal_type == kGenericCard) + return base::string16(); + return CreditCard::TypeForDisplay(internal_type); + } + + default: + NOTREACHED(); + } + + return base::string16(); +} + +bool FullWallet::HasRequiredAction(RequiredAction action) const { + DCHECK(ActionAppliesToFullWallet(action)); + return std::find(required_actions_.begin(), + required_actions_.end(), + action) != required_actions_.end(); +} + +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); +} + +void FullWallet::DecryptCardInfo() { + // base::HexStringToBytes expects an even length string. + if (encrypted_rest_.size() % 2 != 0) + encrypted_rest_ = '0' + encrypted_rest_; + + 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 |one_time_pad_| and |encrypted_rest_| are of the same length + // otherwise something has gone wrong and we can't decrypt the data. + DCHECK_EQ(one_time_pad_.size(), operating_data.size()); + + std::vector<uint8> results; + // XOR |otp| with the encrypted data to decrypt. + for (size_t i = 0; i < one_time_pad_.size(); ++i) + results.push_back(one_time_pad_[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(&(results[0]), results.size()); + + 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); +} + +const std::string& FullWallet::GetPan() { + if (pan_.empty()) + DecryptCardInfo(); + return pan_; +} + +const std::string& FullWallet::GetCvn() { + if (cvn_.empty()) + DecryptCardInfo(); + return cvn_; +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/content/browser/wallet/full_wallet.h b/components/autofill/content/browser/wallet/full_wallet.h new file mode 100644 index 0000000..708e52a --- /dev/null +++ b/components/autofill/content/browser/wallet/full_wallet.h @@ -0,0 +1,130 @@ +// Copyright 2013 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 COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_FULL_WALLET_H_ +#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_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 "components/autofill/content/browser/wallet/required_action.h" +#include "components/autofill/content/browser/wallet/wallet_address.h" + +namespace base { +class DictionaryValue; +} + +namespace autofill { +namespace wallet { + +class FullWalletTest; + +// FullWallet contains all the information a merchant requires from a user for +// that user to make a purchase. This includes: +// - billing information +// - shipping information +// - a proxy card for the backing card selected from a user's wallet items +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); + + // Returns corresponding data for |type|. + base::string16 GetInfo(AutofillFieldType type); + + // Whether or not |action| is in |required_actions_|. + bool HasRequiredAction(RequiredAction action) const; + + 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<RequiredAction>& required_actions() const { + return required_actions_; + } + int expiration_month() const { return expiration_month_; } + int expiration_year() const { return expiration_year_; } + + void set_one_time_pad(const std::vector<uint8>& one_time_pad) { + one_time_pad_ = one_time_pad; + } + + private: + friend class FullWalletTest; + friend scoped_ptr<FullWallet> GetTestFullWallet(); + FRIEND_TEST_ALL_PREFIXES(FullWalletTest, CreateFullWallet); + FRIEND_TEST_ALL_PREFIXES(FullWalletTest, CreateFullWalletWithRequiredActions); + FRIEND_TEST_ALL_PREFIXES(FullWalletTest, EvenRestDecryptionTest); + FRIEND_TEST_ALL_PREFIXES(FullWalletTest, OddRestDecryptionTest); + FRIEND_TEST_ALL_PREFIXES(FullWalletTest, GetCreditCardInfo); + 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<RequiredAction>& required_actions); + + // Decrypts both |pan_| and |cvn_|. + void DecryptCardInfo(); + + // Decrypts and returns the primary account number (PAN) using the generated + // one time pad, |one_time_pad_|. + const std::string& GetPan(); + + // Decrypts and returns the card verification number (CVN) using the generated + // one time pad, |one_time_pad_|. + const std::string& GetCvn(); + + // The expiration month of the proxy card. It should be 1-12. + int expiration_month_; + + // The expiration year of the proxy card. It should be a 4-digit year. + 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_; + + // The billing address of the backing instrument. + scoped_ptr<Address> billing_address_; + + // The shipping address for the transaction. + 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. + std::vector<RequiredAction> required_actions_; + + // The one time pad used for FullWallet encryption. + std::vector<uint8> one_time_pad_; + + DISALLOW_COPY_AND_ASSIGN(FullWallet); +}; + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_FULL_WALLET_H_ diff --git a/components/autofill/content/browser/wallet/full_wallet_unittest.cc b/components/autofill/content/browser/wallet/full_wallet_unittest.cc new file mode 100644 index 0000000..910032b --- /dev/null +++ b/components/autofill/content/browser/wallet/full_wallet_unittest.cc @@ -0,0 +1,505 @@ +// Copyright 2013 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/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "components/autofill/content/browser/wallet/full_wallet.h" +#include "components/autofill/content/browser/wallet/required_action.h" +#include "components/autofill/content/browser/wallet/wallet_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const char kFullWalletValidResponse[] = + "{" + " \"expiration_month\":12," + " \"expiration_year\":3000," + " \"iin\":\"iin\"," + " \"rest\":\"rest\"," + " \"billing_address\":" + " {" + " \"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\":\"admin_area_name\"," + " \"postal_code_number\":\"postal_code_number\"," + " \"country_name_code\":\"US\"" + " }" + " }," + " \"shipping_address\":" + " {" + " \"id\":\"address_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\":\"US\"" + " }" + " }," + " \"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\":\"address_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\":\"address_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\":\"address_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\":\"address_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\":\"address_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\":" + " [" + " \"CHOOSE_ANOTHER_INSTRUMENT_OR_ADDRESS\"," + " \"update_EXPIRATION_date\"," + " \"verify_CVV\"," + " \" REQuIrE_PHONE_NumBER\t\n\r \"" + " ]" + "}"; + +const char kFullWalletWithInvalidRequiredActions[] = + "{" + " \"required_action\":" + " [" + " \" setup_wallet\"," + " \"AcCePt_ToS \"," + " \"UPGRADE_MIN_ADDRESS\"," + " \"INVALID_form_field\"," + " \" \\tGAIA_auth \\n\\r\"," + " \"PASSIVE_GAIA_AUTH\"," + " \" 忍者の正体 \"" + " ]" + "}"; + +const char kFullWalletMalformedBillingAddress[] = + "{" + " \"expiration_month\":12," + " \"expiration_year\":2012," + " \"iin\":\"iin\"," + " \"rest\":\"rest\"," + " \"billing_address\":" + " {" + " \"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\":\"address_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 autofill { +namespace wallet { + +class FullWalletTest : public testing::Test { + public: + FullWalletTest() {} + protected: + void SetUpDictionary(const std::string& json) { + scoped_ptr<Value> value(base::JSONReader::Read(json)); + ASSERT_TRUE(value.get()); + ASSERT_TRUE(value->IsType(Value::TYPE_DICTIONARY)); + dict.reset(static_cast<DictionaryValue*>(value.release())); + } + scoped_ptr<DictionaryValue> dict; +}; + +TEST_F(FullWalletTest, CreateFullWalletMissingExpirationMonth) { + SetUpDictionary(kFullWalletMissingExpirationMonth); + EXPECT_EQ(NULL, FullWallet::CreateFullWallet(*dict).get()); +} + +TEST_F(FullWalletTest, CreateFullWalletMissingExpirationYear) { + SetUpDictionary(kFullWalletMissingExpirationYear); + EXPECT_EQ(NULL, FullWallet::CreateFullWallet(*dict).get()); +} + +TEST_F(FullWalletTest, CreateFullWalletMissingIin) { + SetUpDictionary(kFullWalletMissingIin); + EXPECT_EQ(NULL, FullWallet::CreateFullWallet(*dict).get()); +} + +TEST_F(FullWalletTest, CreateFullWalletMissingRest) { + SetUpDictionary(kFullWalletMissingRest); + EXPECT_EQ(NULL, FullWallet::CreateFullWallet(*dict).get()); +} + +TEST_F(FullWalletTest, CreateFullWalletMissingBillingAddress) { + SetUpDictionary(kFullWalletMissingBillingAddress); + EXPECT_EQ(NULL, FullWallet::CreateFullWallet(*dict).get()); +} + +TEST_F(FullWalletTest, CreateFullWalletMalformedBillingAddress) { + SetUpDictionary(kFullWalletMalformedBillingAddress); + EXPECT_EQ(NULL, FullWallet::CreateFullWallet(*dict).get()); +} + +TEST_F(FullWalletTest, CreateFullWalletWithRequiredActions) { + SetUpDictionary(kFullWalletWithRequiredActions); + + std::vector<RequiredAction> required_actions; + required_actions.push_back(CHOOSE_ANOTHER_INSTRUMENT_OR_ADDRESS); + required_actions.push_back(UPDATE_EXPIRATION_DATE); + required_actions.push_back(VERIFY_CVV); + required_actions.push_back(REQUIRE_PHONE_NUMBER); + + FullWallet full_wallet(-1, + -1, + std::string(), + std::string(), + scoped_ptr<Address>(), + scoped_ptr<Address>(), + required_actions); + EXPECT_EQ(full_wallet, *FullWallet::CreateFullWallet(*dict)); + + ASSERT_FALSE(required_actions.empty()); + required_actions.pop_back(); + FullWallet different_required_actions(-1, + -1, + std::string(), + std::string(), + scoped_ptr<Address>(), + scoped_ptr<Address>(), + required_actions); + EXPECT_NE(full_wallet, different_required_actions); +} + +TEST_F(FullWalletTest, CreateFullWalletWithInvalidRequiredActions) { + SetUpDictionary(kFullWalletWithInvalidRequiredActions); + EXPECT_EQ(NULL, FullWallet::CreateFullWallet(*dict).get()); +} + +TEST_F(FullWalletTest, CreateFullWallet) { + SetUpDictionary(kFullWalletValidResponse); + std::vector<RequiredAction> required_actions; + FullWallet full_wallet(12, + 3000, + "iin", + "rest", + GetTestAddress(), + GetTestNonDefaultShippingAddress(), + required_actions); + EXPECT_EQ(full_wallet, *FullWallet::CreateFullWallet(*dict)); +} + +TEST_F(FullWalletTest, EvenRestDecryptionTest) { + std::vector<RequiredAction> required_actions; + FullWallet full_wallet(12, + 2012, + "528512", + "5ec4feecf9d6", + GetTestAddress(), + GetTestShippingAddress(), + required_actions); + std::vector<uint8> one_time_pad; + EXPECT_TRUE(base::HexStringToBytes("5F04A8704183", &one_time_pad)); + full_wallet.set_one_time_pad(one_time_pad); + EXPECT_EQ(ASCIIToUTF16("5285121925598459"), + full_wallet.GetInfo(CREDIT_CARD_NUMBER)); + EXPECT_EQ(ASCIIToUTF16("989"), + full_wallet.GetInfo(CREDIT_CARD_VERIFICATION_CODE)); +} + +TEST_F(FullWalletTest, OddRestDecryptionTest) { + std::vector<RequiredAction> required_actions; + FullWallet full_wallet(12, + 2012, + "528512", + "1a068673eb0", + GetTestAddress(), + GetTestShippingAddress(), + required_actions); + std::vector<uint8> one_time_pad; + EXPECT_TRUE(base::HexStringToBytes("075DA779F98B", &one_time_pad)); + full_wallet.set_one_time_pad(one_time_pad); + EXPECT_EQ(ASCIIToUTF16("5285127687171393"), + full_wallet.GetInfo(CREDIT_CARD_NUMBER)); + EXPECT_EQ(ASCIIToUTF16("339"), + full_wallet.GetInfo(CREDIT_CARD_VERIFICATION_CODE)); +} + +TEST_F(FullWalletTest, GetCreditCardInfo) { + std::vector<RequiredAction> required_actions; + FullWallet full_wallet(12, + 2015, + "528512", + "1a068673eb0", + GetTestAddress(), + GetTestShippingAddress(), + required_actions); + + EXPECT_EQ(ASCIIToUTF16("15"), + full_wallet.GetInfo(CREDIT_CARD_EXP_2_DIGIT_YEAR)); + + EXPECT_EQ(ASCIIToUTF16("12/15"), + full_wallet.GetInfo(CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR)); + + EXPECT_EQ(ASCIIToUTF16("12/2015"), + full_wallet.GetInfo(CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR)); + + std::vector<uint8> one_time_pad; + EXPECT_TRUE(base::HexStringToBytes("075DA779F98B", &one_time_pad)); + full_wallet.set_one_time_pad(one_time_pad); + EXPECT_EQ(ASCIIToUTF16("MasterCard"), + full_wallet.GetInfo(CREDIT_CARD_TYPE)); +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/content/browser/wallet/instrument.cc b/components/autofill/content/browser/wallet/instrument.cc new file mode 100644 index 0000000..6c94b60 --- /dev/null +++ b/components/autofill/content/browser/wallet/instrument.cc @@ -0,0 +1,141 @@ +// Copyright 2013 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 "components/autofill/content/browser/wallet/instrument.h" + +#include "base/logging.h" +#include "base/string_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "components/autofill/browser/autofill_country.h" +#include "components/autofill/browser/autofill_profile.h" +#include "components/autofill/browser/credit_card.h" +#include "components/autofill/browser/validation.h" +#include "components/autofill/content/browser/wallet/wallet_address.h" + +namespace autofill { +namespace wallet { + +namespace { + +// Converts a known Autofill card type to a Instrument::FormOfPayment. +// Used for creating new Instruments. +Instrument::FormOfPayment FormOfPaymentFromCardType(const std::string& type) { + if (type == kAmericanExpressCard) + return Instrument::AMEX; + else if (type == kDiscoverCard) + return Instrument::DISCOVER; + else if (type == kMasterCard) + return Instrument::MASTER_CARD; + else if (type == kVisaCard) + return Instrument::VISA; + + return Instrument::UNKNOWN; +} + +std::string FormOfPaymentToString(Instrument::FormOfPayment form_of_payment) { + switch (form_of_payment) { + case Instrument::UNKNOWN: + return "UNKNOWN"; + case Instrument::VISA: + return "VISA"; + case Instrument::MASTER_CARD: + return "MASTER_CARD"; + case Instrument::AMEX: + return "AMEX"; + case Instrument::DISCOVER: + return "DISCOVER"; + case Instrument::JCB: + return "JCB"; + } + NOTREACHED(); + return "NOT_POSSIBLE"; +} + +} // namespace + +Instrument::Instrument(const CreditCard& card, + const base::string16& card_verification_number, + const AutofillProfile& profile) + : primary_account_number_(card.GetRawInfo(CREDIT_CARD_NUMBER)), + card_verification_number_(card_verification_number), + expiration_month_(card.expiration_month()), + expiration_year_(card.expiration_year()), + form_of_payment_(FormOfPaymentFromCardType(card.type())), + address_(new Address(profile)) { + Init(); +} + +Instrument::Instrument(const base::string16& primary_account_number, + const base::string16& card_verification_number, + int expiration_month, + int expiration_year, + FormOfPayment form_of_payment, + scoped_ptr<Address> address) + : primary_account_number_(primary_account_number), + card_verification_number_(card_verification_number), + expiration_month_(expiration_month), + expiration_year_(expiration_year), + form_of_payment_(form_of_payment), + address_(address.Pass()) { + DCHECK(address_); + Init(); +} + +Instrument::Instrument(const Instrument& instrument) + : primary_account_number_(instrument.primary_account_number()), + card_verification_number_(instrument.card_verification_number()), + expiration_month_(instrument.expiration_month()), + expiration_year_(instrument.expiration_year()), + form_of_payment_(instrument.form_of_payment()), + address_(new Address(instrument.address())) { + Init(); +} + +Instrument::~Instrument() {} + +scoped_ptr<base::DictionaryValue> Instrument::ToDictionary() const { + // |primary_account_number_| and |card_verification_number_| can never be + // sent the server in way that would require putting them into a dictionary. + // Never add them to this function. + + scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); + dict->SetString("type", "CREDIT_CARD"); + dict->SetInteger("credit_card.exp_month", expiration_month_); + dict->SetInteger("credit_card.exp_year", expiration_year_); + dict->SetString("credit_card.fop_type", + FormOfPaymentToString(form_of_payment_)); + dict->SetString("credit_card.last_4_digits", last_four_digits_); + dict->Set("credit_card.address", + address_.get()->ToDictionaryWithoutID().release()); + + return dict.Pass(); +} + +bool Instrument::IsValid() const { + if (!IsStringASCII(primary_account_number_)) + return false; + bool primary_account_number_valid = + autofill::IsValidCreditCardNumber(primary_account_number_); + bool card_verification_number_valid = card_verification_number_.size() == 3 || + card_verification_number_.size() == 4; + bool exp_month_valid = expiration_month_ >= 1 && expiration_month_ <= 12; + bool exp_year_valid = expiration_year_ >= 2013 && expiration_year_ <= 2100; + + return primary_account_number_valid && + card_verification_number_valid && + exp_month_valid && + exp_year_valid; +} + +void Instrument::Init() { + if (primary_account_number_.size() >= 4) { + last_four_digits_ = + primary_account_number_.substr(primary_account_number_.size() - 4); + } +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/content/browser/wallet/instrument.h b/components/autofill/content/browser/wallet/instrument.h new file mode 100644 index 0000000..97ee2e8 --- /dev/null +++ b/components/autofill/content/browser/wallet/instrument.h @@ -0,0 +1,104 @@ +// Copyright 2013 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 COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_INSTRUMENT_H_ +#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_INSTRUMENT_H_ + +#include <string> +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/string16.h" + +namespace base { +class DictionaryValue; +} + +namespace autofill { + +class AutofillProfile; +class CreditCard; + +namespace wallet { + +class Address; + +// This class contains all the data necessary to save a new instrument to a +// user's Google Wallet using WalletClient::SaveInstrument or +// WalletClient::SaveInstrumentAndAddress. +class Instrument { + public: + enum FormOfPayment { + UNKNOWN, + VISA, + MASTER_CARD, + AMEX, + DISCOVER, + JCB, + }; + + // Convert the info in |card| to an Instrument using |profile| for address. + Instrument(const CreditCard& card, + const base::string16& card_verification_number, + const AutofillProfile& profile); + + Instrument(const base::string16& primary_account_number, + const base::string16& card_verification_number, + int expiration_month, + int expiration_year, + FormOfPayment form_of_payment, + scoped_ptr<Address> address); + + Instrument(const Instrument& instrument); + + ~Instrument(); + + scoped_ptr<base::DictionaryValue> ToDictionary() const; + + // Users of this class should call IsValid to check that the inputs provided + // in the constructor were valid for use with Google Wallet. + bool IsValid() const; + + const base::string16& primary_account_number() const { + return primary_account_number_; + } + const base::string16& card_verification_number() const { + return card_verification_number_; + } + int expiration_month() const { return expiration_month_; } + int expiration_year() const { return expiration_year_; } + const Address& address() const { return *address_; } + FormOfPayment form_of_payment() const { return form_of_payment_; } + const base::string16& last_four_digits() { return last_four_digits_; } + + private: + void Init(); + + // |primary_account_number_| is expected to be \d{12-19}. + base::string16 primary_account_number_; + + // |card_verification_number_| is expected to be \d{3-4}. + base::string16 card_verification_number_; + + // |expiration month_| should be 1-12. + int expiration_month_; + + // |expiration_year_| should be a 4-digit year. + int expiration_year_; + + // The payment network of the instrument, e.g. Visa. + FormOfPayment form_of_payment_; + + // The billing address of the instrument. + scoped_ptr<Address> address_; + + // The last four digits of |primary_account_number_|. + base::string16 last_four_digits_; + + DISALLOW_ASSIGN(Instrument); +}; + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_INSTRUMENT_H_ diff --git a/components/autofill/content/browser/wallet/instrument_unittest.cc b/components/autofill/content/browser/wallet/instrument_unittest.cc new file mode 100644 index 0000000..f4b9ee8 --- /dev/null +++ b/components/autofill/content/browser/wallet/instrument_unittest.cc @@ -0,0 +1,188 @@ +// Copyright 2013 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/strings/utf_string_conversions.h" +#include "base/values.h" +#include "components/autofill/content/browser/wallet/instrument.h" +#include "components/autofill/content/browser/wallet/wallet_address.h" +#include "components/autofill/content/browser/wallet/wallet_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const char kPrimaryAccountNumber[] = "4444444444444448"; +const char kCardVerificationNumber[] = "123"; +const char kLastFourDigits[] = "4448"; + +} + +namespace autofill { +namespace wallet { + +TEST(Instrument, LastFourDigits) { + Instrument instrument(ASCIIToUTF16(kPrimaryAccountNumber), + ASCIIToUTF16(kCardVerificationNumber), + 12, + 2015, + Instrument::VISA, + GetTestShippingAddress().Pass()); + + EXPECT_EQ(ASCIIToUTF16(kLastFourDigits), instrument.last_four_digits()); + EXPECT_TRUE(instrument.IsValid()); +} + +TEST(Instrument, NoPrimaryAccountNumberIsInvalid) { + Instrument instrument(base::string16(), + ASCIIToUTF16(kCardVerificationNumber), + 12, + 2015, + Instrument::VISA, + GetTestShippingAddress().Pass()); + + EXPECT_FALSE(instrument.IsValid()); +} + +TEST(Instrument, TooShortPrimaryAccountNumberIsInvalid) { + Instrument instrument(ASCIIToUTF16("44447"), + ASCIIToUTF16(kCardVerificationNumber), + 12, + 2015, + Instrument::VISA, + GetTestShippingAddress().Pass()); + + EXPECT_FALSE(instrument.IsValid()); +} + +TEST(Instrument, TooLongPrimaryAccountNumberIsInvalid) { + Instrument instrument(ASCIIToUTF16("44444444444444444448"), + ASCIIToUTF16(kCardVerificationNumber), + 12, + 2015, + Instrument::VISA, + GetTestShippingAddress().Pass()); + + EXPECT_FALSE(instrument.IsValid()); +} + +TEST(Instrument, PrimaryAccountNumberNotPassingLuhnIsInvalid) { + Instrument instrument(ASCIIToUTF16("4444444444444444"), + ASCIIToUTF16(kCardVerificationNumber), + 12, + 2015, + Instrument::VISA, + GetTestShippingAddress().Pass()); + + EXPECT_FALSE(instrument.IsValid()); +} + +TEST(Instrument, NoCardVerificationNumberIsInvalid) { + Instrument instrument(ASCIIToUTF16(kPrimaryAccountNumber), + base::string16(), + 12, + 2015, + Instrument::VISA, + GetTestShippingAddress().Pass()); + + EXPECT_FALSE(instrument.IsValid()); +} + +TEST(Instrument, TooShortCardVerificationNumberIsInvalid) { + Instrument instrument(ASCIIToUTF16(kPrimaryAccountNumber), + ASCIIToUTF16("12"), + 12, + 2015, + Instrument::VISA, + GetTestShippingAddress().Pass()); + + EXPECT_FALSE(instrument.IsValid()); +} + +TEST(Instrument, TooLongCardVerificationNumberIsInvalid) { + Instrument instrument(ASCIIToUTF16(kPrimaryAccountNumber), + ASCIIToUTF16("12345"), + 12, + 2015, + Instrument::VISA, + GetTestShippingAddress().Pass()); + + EXPECT_FALSE(instrument.IsValid()); +} + +TEST(Instrument, ZeroAsExpirationMonthIsInvalid) { + Instrument instrument(ASCIIToUTF16(kPrimaryAccountNumber), + ASCIIToUTF16(kCardVerificationNumber), + 0, + 2015, + Instrument::VISA, + GetTestShippingAddress().Pass()); + + EXPECT_FALSE(instrument.IsValid()); +} + +TEST(Instrument, TooLargeExpirationMonthIsInvalid) { + Instrument instrument(ASCIIToUTF16(kPrimaryAccountNumber), + ASCIIToUTF16(kCardVerificationNumber), + 13, + 2015, + Instrument::VISA, + GetTestShippingAddress().Pass()); + + EXPECT_FALSE(instrument.IsValid()); +} + +TEST(Instrument, TooSmallExpirationYearIsInvalid) { + Instrument instrument(ASCIIToUTF16(kPrimaryAccountNumber), + ASCIIToUTF16(kCardVerificationNumber), + 12, + 999, + Instrument::VISA, + GetTestShippingAddress().Pass()); + + EXPECT_FALSE(instrument.IsValid()); +} + +TEST(Instrument, TooLargeExpirationYearIsInvalid) { + Instrument instrument(ASCIIToUTF16(kPrimaryAccountNumber), + ASCIIToUTF16(kCardVerificationNumber), + 12, + 10000, + Instrument::VISA, + GetTestShippingAddress().Pass()); + + EXPECT_FALSE(instrument.IsValid()); +} + +TEST(Instrument, ToDictionary) { + base::DictionaryValue expected; + expected.SetString("type", "CREDIT_CARD"); + expected.SetInteger("credit_card.exp_month", 12); + expected.SetInteger("credit_card.exp_year", 2015); + expected.SetString("credit_card.last_4_digits", kLastFourDigits); + expected.SetString("credit_card.fop_type", "VISA"); + expected.SetString("credit_card.address.country_name_code", "US"); + expected.SetString("credit_card.address.recipient_name", + "ship_recipient_name"); + expected.SetString("credit_card.address.locality_name", + "ship_locality_name"); + expected.SetString("credit_card.address.administrative_area_name", + "ship_admin_area_name"); + expected.SetString("credit_card.address.postal_code_number", + "ship_postal_code_number"); + base::ListValue* address_lines = new base::ListValue(); + address_lines->AppendString("ship_address_line_1"); + address_lines->AppendString("ship_address_line_2"); + expected.Set("credit_card.address.address_line", address_lines); + + Instrument instrument(ASCIIToUTF16(kPrimaryAccountNumber), + ASCIIToUTF16(kCardVerificationNumber), + 12, + 2015, + Instrument::VISA, + GetTestShippingAddress().Pass()); + + EXPECT_TRUE(expected.Equals(instrument.ToDictionary().get())); +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/content/browser/wallet/required_action.cc b/components/autofill/content/browser/wallet/required_action.cc new file mode 100644 index 0000000..716da3e --- /dev/null +++ b/components/autofill/content/browser/wallet/required_action.cc @@ -0,0 +1,66 @@ +// Copyright 2013 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 "components/autofill/content/browser/wallet/required_action.h" + +#include "base/logging.h" +#include "base/string_util.h" + +namespace autofill { +namespace wallet { + +bool ActionAppliesToFullWallet(RequiredAction action) { + return action == UPDATE_EXPIRATION_DATE || + action == VERIFY_CVV || + action == CHOOSE_ANOTHER_INSTRUMENT_OR_ADDRESS || + action == REQUIRE_PHONE_NUMBER; +} + +bool ActionAppliesToSaveToWallet(RequiredAction action) { + return action == INVALID_FORM_FIELD || + action == REQUIRE_PHONE_NUMBER; +} + +bool ActionAppliesToWalletItems(RequiredAction action) { + return action == SETUP_WALLET || + action == CHOOSE_ANOTHER_INSTRUMENT_OR_ADDRESS || + action == ACCEPT_TOS || + action == GAIA_AUTH || + action == REQUIRE_PHONE_NUMBER || + action == UPDATE_EXPIRATION_DATE || + action == UPGRADE_MIN_ADDRESS || + action == PASSIVE_GAIA_AUTH; +} + +RequiredAction ParseRequiredActionFromString(const std::string& str) { + std::string str_lower; + TrimWhitespaceASCII(StringToLowerASCII(str), TRIM_ALL, &str_lower); + + if (str_lower == "setup_wallet") + return SETUP_WALLET; + else if (str_lower == "accept_tos") + return ACCEPT_TOS; + else if (str_lower == "gaia_auth") + return GAIA_AUTH; + else if (str_lower == "update_expiration_date") + return UPDATE_EXPIRATION_DATE; + else if (str_lower == "upgrade_min_address") + return UPGRADE_MIN_ADDRESS; + else if (str_lower == "invalid_form_field") + return INVALID_FORM_FIELD; + else if (str_lower == "verify_cvv") + return VERIFY_CVV; + else if (str_lower == "passive_gaia_auth") + return PASSIVE_GAIA_AUTH; + else if (str_lower == "require_phone_number") + return REQUIRE_PHONE_NUMBER; + else if (str_lower == "choose_another_instrument_or_address") + return CHOOSE_ANOTHER_INSTRUMENT_OR_ADDRESS; + + DLOG(ERROR) << "Failed to parse: \"" << str << "\" as a required action"; + return UNKNOWN_TYPE; +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/content/browser/wallet/required_action.h b/components/autofill/content/browser/wallet/required_action.h new file mode 100644 index 0000000..eb53622 --- /dev/null +++ b/components/autofill/content/browser/wallet/required_action.h @@ -0,0 +1,44 @@ +// Copyright 2013 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 COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_REQUIRED_ACTION_H_ +#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_REQUIRED_ACTION_H_ + +#include <string> + +namespace autofill { +namespace wallet { + +// Required actions are steps that must be taken before the current transaction +// can proceed. Examples of this is include accepting the Terms of Service to +// use Google Wallet (happens on first use or when the ToS are updated) or +// typing a CVC when it's necessary verify the current user has access to the +// backing card. +enum RequiredAction { + UNKNOWN_TYPE = 0, // Catch all type. + CHOOSE_ANOTHER_INSTRUMENT_OR_ADDRESS, + SETUP_WALLET, + ACCEPT_TOS, + GAIA_AUTH, + UPDATE_EXPIRATION_DATE, + UPGRADE_MIN_ADDRESS, + INVALID_FORM_FIELD, + VERIFY_CVV, + PASSIVE_GAIA_AUTH, + REQUIRE_PHONE_NUMBER, +}; + +// Static helper functions to determine if an RequiredAction applies to a +// FullWallet, WalletItems, or SaveToWallet response. +bool ActionAppliesToFullWallet(RequiredAction action); +bool ActionAppliesToSaveToWallet(RequiredAction action); +bool ActionAppliesToWalletItems(RequiredAction action); + +// Turn a string value of the parsed JSON response into an RequiredAction. +RequiredAction ParseRequiredActionFromString(const std::string& str); + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_REQUIRED_ACTION_H_ diff --git a/components/autofill/content/browser/wallet/wallet_address.cc b/components/autofill/content/browser/wallet/wallet_address.cc new file mode 100644 index 0000000..afc27ef --- /dev/null +++ b/components/autofill/content/browser/wallet/wallet_address.cc @@ -0,0 +1,315 @@ +// Copyright 2013 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 "components/autofill/content/browser/wallet/wallet_address.h" + +#include "base/logging.h" +#include "base/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "components/autofill/browser/autofill_country.h" +#include "components/autofill/browser/autofill_profile.h" + +namespace autofill { +namespace wallet { + +// Server specified type for address with complete details. +const char kFullAddress[] = "FULL"; + +namespace { + +Address* CreateAddressInternal(const base::DictionaryValue& dictionary, + const std::string& object_id) { + 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 NULL; + } + + string16 recipient_name; + if (!dictionary.GetString("postal_address.recipient_name", + &recipient_name)) { + DLOG(ERROR) << "Response from Google Wallet missing recipient name"; + return NULL; + } + + string16 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 NULL; + } + + string16 phone_number; + if (!dictionary.GetString("phone_number", &phone_number)) + DVLOG(1) << "Response from Google Wallet missing phone number"; + + string16 address_line_1; + string16 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"; + } + + string16 locality_name; + if (!dictionary.GetString("postal_address.locality_name", + &locality_name)) { + DVLOG(1) << "Response from Google Wallet missing locality name"; + } + + string16 administrative_area_name; + if (!dictionary.GetString("postal_address.administrative_area_name", + &administrative_area_name)) { + DVLOG(1) << "Response from Google Wallet missing administrative area name"; + } + + Address* 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); + + bool is_minimal_address = false; + if (dictionary.GetBoolean("is_minimal_address", &is_minimal_address)) + address->set_is_complete_address(!is_minimal_address); + else + DVLOG(1) << "Response from Google Wallet missing is_minimal_address bit"; + + return address; +} + +} // namespace + +Address::Address() {} + +Address::Address(const AutofillProfile& profile) + : country_name_code_( + UTF16ToASCII(profile.GetRawInfo(ADDRESS_HOME_COUNTRY))), + recipient_name_(profile.GetRawInfo(NAME_FULL)), + address_line_1_(profile.GetRawInfo(ADDRESS_HOME_LINE1)), + address_line_2_(profile.GetRawInfo(ADDRESS_HOME_LINE2)), + locality_name_(profile.GetRawInfo(ADDRESS_HOME_CITY)), + administrative_area_name_(profile.GetRawInfo(ADDRESS_HOME_STATE)), + postal_code_number_(profile.GetRawInfo(ADDRESS_HOME_ZIP)), + phone_number_(profile.GetRawInfo(PHONE_HOME_WHOLE_NUMBER)), + is_complete_address_(true) {} + +Address::Address(const std::string& country_name_code, + const string16& recipient_name, + const string16& address_line_1, + const string16& address_line_2, + const string16& locality_name, + const string16& administrative_area_name, + const string16& postal_code_number, + const string16& 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), + is_complete_address_(true) {} + +Address::~Address() {} + +// static +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>(); + } + return scoped_ptr<Address>(CreateAddressInternal(dictionary, object_id)); +} + +// static +scoped_ptr<Address> Address::CreateAddress( + const base::DictionaryValue& dictionary) { + std::string object_id; + dictionary.GetString("id", &object_id); + return scoped_ptr<Address>(CreateAddressInternal(dictionary, object_id)); +} + +// static +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>(); + } + + string16 name; + if (!dictionary.GetString("name", &name)) { + DLOG(ERROR) << "Reponse from Google Wallet missing name"; + return scoped_ptr<Address>(); + } + + string16 postal_code; + if (!dictionary.GetString("postal_code", &postal_code)) { + DLOG(ERROR) << "Reponse from Google Wallet missing postal code"; + return scoped_ptr<Address>(); + } + + string16 address1; + if (!dictionary.GetString("address1", &address1)) + DVLOG(1) << "Reponse from Google Wallet missing address1"; + + string16 address2; + if (!dictionary.GetString("address2", &address2)) + DVLOG(1) << "Reponse from Google Wallet missing address2"; + + string16 city; + if (!dictionary.GetString("city", &city)) + DVLOG(1) << "Reponse from Google Wallet missing city"; + + string16 state; + if (!dictionary.GetString("state", &state)) + DVLOG(1) << "Reponse from Google Wallet missing state"; + + string16 phone_number; + if (!dictionary.GetString("phone_number", &phone_number)) + DVLOG(1) << "Reponse from Google Wallet missing phone number"; + + std::string address_state; + if (!dictionary.GetString("type", &address_state)) + DVLOG(1) << "Response from Google Wallet missing type/state of address"; + + scoped_ptr<Address> address( + new Address(country_code, + name, + address1, + address2, + city, + state, + postal_code, + phone_number, + std::string())); + address->set_is_complete_address(address_state == kFullAddress); + + return address.Pass(); +} + +scoped_ptr<base::DictionaryValue> Address::ToDictionaryWithID() const { + scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); + + if (!object_id_.empty()) + dict->SetString("id", object_id_); + dict->SetString("phone_number", phone_number_); + dict->Set("postal_address", ToDictionaryWithoutID().release()); + + return dict.Pass(); +} + +scoped_ptr<base::DictionaryValue> Address::ToDictionaryWithoutID() const { + scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); + + scoped_ptr<base::ListValue> address_lines(new base::ListValue()); + address_lines->AppendString(address_line_1_); + if (!address_line_2_.empty()) + address_lines->AppendString(address_line_2_); + dict->Set("address_line", address_lines.release()); + + dict->SetString("country_name_code", country_name_code_); + dict->SetString("recipient_name", recipient_name_); + dict->SetString("locality_name", locality_name_); + dict->SetString("administrative_area_name", + administrative_area_name_); + dict->SetString("postal_code_number", postal_code_number_); + + return dict.Pass(); +} + +string16 Address::DisplayName() const { +#if defined(OS_ANDROID) + // TODO(aruslan): improve this stub implementation. + return recipient_name(); +#else + // TODO(estade): improve this stub implementation + l10n. + return recipient_name() + ASCIIToUTF16(", ") + address_line_1(); +#endif +} + +string16 Address::DisplayNameDetail() const { +#if defined(OS_ANDROID) + // TODO(aruslan): improve this stub implementation. + return address_line_1(); +#else + return string16(); +#endif +} + +string16 Address::GetInfo(AutofillFieldType type, + const std::string& app_locale) const { + switch (AutofillType::GetEquivalentFieldType(type)) { + case NAME_FULL: + return recipient_name(); + + case ADDRESS_HOME_LINE1: + return address_line_1(); + + case ADDRESS_HOME_LINE2: + return address_line_2(); + + case ADDRESS_HOME_CITY: + return locality_name(); + + case ADDRESS_HOME_STATE: + return administrative_area_name(); + + case ADDRESS_HOME_ZIP: + return postal_code_number(); + + case ADDRESS_HOME_COUNTRY: { + AutofillCountry country(country_name_code(), app_locale); + return country.name(); + } + + case PHONE_HOME_WHOLE_NUMBER: + return phone_number(); + + // TODO(estade): implement more. + default: + NOTREACHED(); + return string16(); + } +} + +bool Address::EqualsIgnoreID(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_ && + is_complete_address_ == other.is_complete_address_; +} + +bool Address::operator==(const Address& other) const { + return object_id_ == other.object_id_ && EqualsIgnoreID(other); +} + +bool Address::operator!=(const Address& other) const { + return !(*this == other); +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/content/browser/wallet/wallet_address.h b/components/autofill/content/browser/wallet/wallet_address.h new file mode 100644 index 0000000..87a335e --- /dev/null +++ b/components/autofill/content/browser/wallet/wallet_address.h @@ -0,0 +1,200 @@ +// Copyright 2013 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 COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_WALLET_ADDRESS_H_ +#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_WALLET_ADDRESS_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/string16.h" +#include "components/autofill/browser/field_types.h" + +namespace base { +class DictionaryValue; +} + +namespace autofill { + +class AutofillProfile; + +namespace wallet { + +// TODO(ahutter): This address is a lot like +// components/autofill/browser/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. It is loosely modeled as a subet of the OASIS +// "extensible Address Language" (xAL); see +// http://www.oasis-open.org/committees/ciq/download.shtml. +class Address { + public: + // TODO(ahutter): Use additional fields (descriptive_name, is_post_box, + // is_valid, is_default) when SaveToWallet is implemented. + // See http://crbug.com/164284. + + Address(); + + // Using the raw info in |profile|, create a wallet::Address. + explicit Address(const AutofillProfile& profile); + + Address(const std::string& country_name_code, + const base::string16& recipient_name, + const base::string16& address_line_1, + const base::string16& address_line_2, + const base::string16& locality_name, + const base::string16& administrative_area_name, + const base::string16& postal_code_number, + const base::string16& phone_number, + const std::string& object_id); + + ~Address(); + + // Returns an empty scoped_ptr if input is invalid or a valid address that is + // selectable for Google Wallet use. Does not require "id" in |dictionary|. + // IDs are not required for billing addresses. + static scoped_ptr<Address> CreateAddress( + const base::DictionaryValue& dictionary); + + // TODO(ahutter): Make obvious in the function name that this public method + // only works for shipping address and assumes existance of "postal_address". + // Builds an Address from |dictionary|, which must have an "id" field. This + // function is designed for use with shipping addresses. The function may fail + // and return an empty pointer if its input is invalid. + 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); + + // If an address is being upgraded, it will be sent to the server in a + // different format and with a few additional fields set, most importantly + // |object_id_|. + scoped_ptr<base::DictionaryValue> ToDictionaryWithID() const; + + // Newly created addresses will not have an associated |object_id_| and are + // sent to the server in a slightly different format. + scoped_ptr<base::DictionaryValue> ToDictionaryWithoutID() const; + + // Returns a string that summarizes this address, suitable for display to + // the user. + base::string16 DisplayName() const; + + // Returns a string that could be used as a sub-label, suitable for display + // to the user together with DisplayName(). + base::string16 DisplayNameDetail() const; + + // Returns data appropriate for |type|. + base::string16 GetInfo(AutofillFieldType type, + const std::string& app_locale) const; + + const std::string& country_name_code() const { return country_name_code_; } + const base::string16& recipient_name() const { return recipient_name_; } + const base::string16& address_line_1() const { return address_line_1_; } + const base::string16& address_line_2() const { return address_line_2_; } + const base::string16& locality_name() const { return locality_name_; } + const base::string16& administrative_area_name() const { + return administrative_area_name_; + } + const base::string16& postal_code_number() const { + return postal_code_number_; + } + const base::string16& phone_number() const { return phone_number_; } + const std::string& object_id() const { return object_id_; } + bool is_complete_address() const { + return is_complete_address_; + } + + void set_country_name_code(const std::string& country_name_code) { + country_name_code_ = country_name_code; + } + void set_recipient_name(const base::string16& recipient_name) { + recipient_name_ = recipient_name; + } + void set_address_line_1(const base::string16& address_line_1) { + address_line_1_ = address_line_1; + } + void set_address_line_2(const base::string16& address_line_2) { + address_line_2_ = address_line_2; + } + void set_locality_name(const base::string16& locality_name) { + locality_name_ = locality_name; + } + void set_administrative_area_name( + const base::string16& administrative_area_name) { + administrative_area_name_ = administrative_area_name; + } + void set_postal_code_number(const base::string16& postal_code_number) { + postal_code_number_ = postal_code_number; + } + void set_phone_number(const base::string16& phone_number) { + phone_number_ = phone_number; + } + void set_object_id(const std::string& object_id) { + object_id_ = object_id; + } + void set_is_complete_address(bool is_complete_address) { + is_complete_address_ = is_complete_address; + } + + // Tests if this address exact matches |other|. |object_id| is ignored. + bool EqualsIgnoreID(const Address& other) const; + + // Tests if this address exact matches |other| including |object_id|. + bool operator==(const Address& other) const; + bool operator!=(const Address& other) const; + + private: + // |country_name_code_| should be an ISO 3166-1-alpha-2 (two letter codes, as + // used in DNS). For example, "GB". + std::string country_name_code_; + + // The recipient's name. For example "John Doe". + base::string16 recipient_name_; + + // |address_line_1| and |address_line_2| correspond to the "AddressLine" + // elements in xAL, which are used to hold unstructured text. + base::string16 address_line_1_; + base::string16 address_line_2_; + + // Locality. This is something of a fuzzy term, but it generally refers to + // the city/town portion of an address. In regions of the world where + // localities are not well defined or do not fit into this structure well + // (for example, Japan and China), leave locality_name empty and use + // |address_line_2|. + // Examples: US city, IT comune, UK post town. + base::string16 locality_name_; + + // Top-level administrative subdivision of this country. + // Examples: US state, IT region, UK constituent nation, JP prefecture. + base::string16 administrative_area_name_; + + // Despite the name, |postal_code_number_| values are frequently alphanumeric. + // Examples: "94043", "SW1W", "SW1W 9TQ". + base::string16 postal_code_number_; + + // A valid international phone number. If |phone_number_| is a user provided + // value, it should have been validated using libphonenumber by clients of + // this class before being set; see http://code.google.com/p/libphonenumber/. + base::string16 phone_number_; + + // Externalized Online Wallet id for this address. + std::string object_id_; + + // Server's understanding of this address as complete address or not. + bool is_complete_address_; + + // This class is intentionally copyable. + DISALLOW_ASSIGN(Address); +}; + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_WALLET_ADDRESS_H_ diff --git a/components/autofill/content/browser/wallet/wallet_address_unittest.cc b/components/autofill/content/browser/wallet/wallet_address_unittest.cc new file mode 100644 index 0000000..924037c --- /dev/null +++ b/components/autofill/content/browser/wallet/wallet_address_unittest.cc @@ -0,0 +1,381 @@ +// Copyright 2013 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/strings/utf_string_conversions.h" +#include "base/values.h" +#include "components/autofill/content/browser/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\"," + " \"is_minimal_address\":true," + " \"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\"," + " \"type\":\"FULL\"" + "}"; + +} // anonymous namespace + +namespace autofill { +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, AddressEqualsIgnoreID) { + Address address1("country_name_code", + ASCIIToUTF16("recipient_name"), + ASCIIToUTF16("address_line_1"), + ASCIIToUTF16("address_line_2"), + ASCIIToUTF16("locality_name"), + ASCIIToUTF16("administrative_area_name"), + ASCIIToUTF16("postal_code_number"), + ASCIIToUTF16("phone_number"), + "id1"); + // Same as address1, only id is different. + Address address2("country_name_code", + ASCIIToUTF16("recipient_name"), + ASCIIToUTF16("address_line_1"), + ASCIIToUTF16("address_line_2"), + ASCIIToUTF16("locality_name"), + ASCIIToUTF16("administrative_area_name"), + ASCIIToUTF16("postal_code_number"), + ASCIIToUTF16("phone_number"), + "id2"); + // Has same id as address1, but name is different. + Address address3("country_name_code", + ASCIIToUTF16("a_different_name"), + ASCIIToUTF16("address_line_1"), + ASCIIToUTF16("address_line_2"), + ASCIIToUTF16("locality_name"), + ASCIIToUTF16("administrative_area_name"), + ASCIIToUTF16("postal_code_number"), + ASCIIToUTF16("phone_number"), + "id1"); + // Same as address1, but no id. + Address address4("country_name_code", + ASCIIToUTF16("recipient_name"), + ASCIIToUTF16("address_line_1"), + ASCIIToUTF16("address_line_2"), + ASCIIToUTF16("locality_name"), + ASCIIToUTF16("administrative_area_name"), + ASCIIToUTF16("postal_code_number"), + ASCIIToUTF16("phone_number"), + std::string()); + + // Compare the address has id field to itself. + EXPECT_EQ(address1, address1); + EXPECT_TRUE(address1.EqualsIgnoreID(address1)); + + // Compare the address has no id field to itself + EXPECT_EQ(address4, address4); + EXPECT_TRUE(address4.EqualsIgnoreID(address4)); + + // Compare two addresses with different id. + EXPECT_NE(address1, address2); + EXPECT_TRUE(address1.EqualsIgnoreID(address2)); + EXPECT_TRUE(address2.EqualsIgnoreID(address1)); + + // Compare two different addresses. + EXPECT_NE(address1, address3); + EXPECT_FALSE(address1.EqualsIgnoreID(address3)); + EXPECT_FALSE(address3.EqualsIgnoreID(address1)); + + // Compare two same addresses, one has id, the other doesn't. + EXPECT_NE(address1, address4); + EXPECT_TRUE(address1.EqualsIgnoreID(address4)); + EXPECT_TRUE(address4.EqualsIgnoreID(address1)); +} + +TEST_F(WalletAddressTest, CreateAddressMissingObjectId) { + SetUpDictionary(kAddressMissingObjectId); + Address address("country_name_code", + ASCIIToUTF16("recipient_name"), + ASCIIToUTF16("address_line_1"), + ASCIIToUTF16("address_line_2"), + ASCIIToUTF16("locality_name"), + ASCIIToUTF16("administrative_area_name"), + ASCIIToUTF16("postal_code_number"), + ASCIIToUTF16("phone_number"), + std::string()); + EXPECT_EQ(address, *Address::CreateAddress(*dict_)); +} + +TEST_F(WalletAddressTest, CreateAddressWithIDMissingObjectId) { + SetUpDictionary(kAddressMissingObjectId); + EXPECT_EQ(NULL, Address::CreateAddressWithID(*dict_).get()); +} + +TEST_F(WalletAddressTest, CreateAddressMissingCountryNameCode) { + SetUpDictionary(kAddressMissingCountryNameCode); + EXPECT_EQ(NULL, Address::CreateAddress(*dict_).get()); + EXPECT_EQ(NULL, Address::CreateAddressWithID(*dict_).get()); +} + +TEST_F(WalletAddressTest, CreateAddressMissingRecipientName) { + SetUpDictionary(kAddressMissingRecipientName); + EXPECT_EQ(NULL, Address::CreateAddress(*dict_).get()); + EXPECT_EQ(NULL, Address::CreateAddressWithID(*dict_).get()); +} + +TEST_F(WalletAddressTest, CreateAddressMissingPostalCodeNumber) { + SetUpDictionary(kAddressMissingPostalCodeNumber); + EXPECT_EQ(NULL, Address::CreateAddress(*dict_).get()); + EXPECT_EQ(NULL, Address::CreateAddressWithID(*dict_).get()); +} + +TEST_F(WalletAddressTest, CreateAddressWithID) { + SetUpDictionary(kValidAddress); + Address address("country_name_code", + ASCIIToUTF16("recipient_name"), + ASCIIToUTF16("address_line_1"), + ASCIIToUTF16("address_line_2"), + ASCIIToUTF16("locality_name"), + ASCIIToUTF16("administrative_area_name"), + ASCIIToUTF16("postal_code_number"), + ASCIIToUTF16("phone_number"), + "id"); + address.set_is_complete_address(false); + EXPECT_EQ(address, *Address::CreateAddress(*dict_)); + EXPECT_EQ(address, *Address::CreateAddressWithID(*dict_)); +} + +TEST_F(WalletAddressTest, CreateDisplayAddressMissingCountryNameCode) { + SetUpDictionary(kClientAddressMissingCountryCode); + EXPECT_EQ(NULL, Address::CreateDisplayAddress(*dict_).get()); +} + +TEST_F(WalletAddressTest, CreateDisplayAddressMissingName) { + SetUpDictionary(kClientAddressMissingName); + EXPECT_EQ(NULL, Address::CreateDisplayAddress(*dict_).get()); +} + +TEST_F(WalletAddressTest, CreateDisplayAddressMissingPostalCode) { + SetUpDictionary(kClientAddressMissingPostalCode); + EXPECT_EQ(NULL, Address::CreateDisplayAddress(*dict_).get()); +} + +TEST_F(WalletAddressTest, CreateDisplayAddress) { + SetUpDictionary(kClientValidAddress); + Address address("country_code", + ASCIIToUTF16("name"), + ASCIIToUTF16("address1"), + ASCIIToUTF16("address2"), + ASCIIToUTF16("city"), + ASCIIToUTF16("state"), + ASCIIToUTF16("postal_code"), + ASCIIToUTF16("phone_number"), + std::string()); + EXPECT_EQ(address, *Address::CreateDisplayAddress(*dict_)); +} + +TEST_F(WalletAddressTest, ToDictionaryWithoutID) { + base::DictionaryValue expected; + expected.SetString("country_name_code", + "country_name_code"); + expected.SetString("recipient_name", + "recipient_name"); + expected.SetString("locality_name", + "locality_name"); + expected.SetString("administrative_area_name", + "administrative_area_name"); + expected.SetString("postal_code_number", + "postal_code_number"); + base::ListValue* address_lines = new base::ListValue(); + address_lines->AppendString("address_line_1"); + address_lines->AppendString("address_line_2"); + expected.Set("address_line", address_lines); + + Address address("country_name_code", + ASCIIToUTF16("recipient_name"), + ASCIIToUTF16("address_line_1"), + ASCIIToUTF16("address_line_2"), + ASCIIToUTF16("locality_name"), + ASCIIToUTF16("administrative_area_name"), + ASCIIToUTF16("postal_code_number"), + ASCIIToUTF16("phone_number"), + std::string()); + + EXPECT_TRUE(expected.Equals(address.ToDictionaryWithoutID().get())); +} + +TEST_F(WalletAddressTest, ToDictionaryWithID) { + base::DictionaryValue expected; + expected.SetString("id", "id"); + expected.SetString("phone_number", "phone_number"); + expected.SetString("postal_address.country_name_code", + "country_name_code"); + expected.SetString("postal_address.recipient_name", + "recipient_name"); + expected.SetString("postal_address.locality_name", + "locality_name"); + expected.SetString("postal_address.administrative_area_name", + "administrative_area_name"); + expected.SetString("postal_address.postal_code_number", + "postal_code_number"); + base::ListValue* address_lines = new base::ListValue(); + address_lines->AppendString("address_line_1"); + address_lines->AppendString("address_line_2"); + expected.Set("postal_address.address_line", address_lines); + + Address address("country_name_code", + ASCIIToUTF16("recipient_name"), + ASCIIToUTF16("address_line_1"), + ASCIIToUTF16("address_line_2"), + ASCIIToUTF16("locality_name"), + ASCIIToUTF16("administrative_area_name"), + ASCIIToUTF16("postal_code_number"), + ASCIIToUTF16("phone_number"), + "id"); + + EXPECT_TRUE(expected.Equals(address.ToDictionaryWithID().get())); +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/content/browser/wallet/wallet_client.cc b/components/autofill/content/browser/wallet/wallet_client.cc new file mode 100644 index 0000000..59e10c1 --- /dev/null +++ b/components/autofill/content/browser/wallet/wallet_client.cc @@ -0,0 +1,975 @@ +// Copyright 2013 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 "components/autofill/content/browser/wallet/wallet_client.h" + +#include "base/bind.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_util.h" +#include "components/autofill/browser/autofill_metrics.h" +#include "components/autofill/content/browser/wallet/instrument.h" +#include "components/autofill/content/browser/wallet/wallet_address.h" +#include "components/autofill/content/browser/wallet/wallet_client_delegate.h" +#include "components/autofill/content/browser/wallet/wallet_items.h" +#include "components/autofill/content/browser/wallet/wallet_service_url.h" +#include "crypto/random.h" +#include "google_apis/google_api_keys.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 autofill { +namespace wallet { + +namespace { + +const char kJsonMimeType[] = "application/json"; +const size_t kOneTimePadLength = 6; + +std::string AutocheckoutStatusToString(AutocheckoutStatus status) { + switch (status) { + case MISSING_FIELDMAPPING: + return "MISSING_FIELDMAPPING"; + case MISSING_ADVANCE: + return "MISSING_ADVANCE"; + case CANNOT_PROCEED: + return "CANNOT_PROCEED"; + case SUCCESS: + // SUCCESS cannot be sent to the server as it will result in a failure. + NOTREACHED(); + return "ERROR"; + } + NOTREACHED(); + return "NOT_POSSIBLE"; +} + +std::string DialogTypeToFeatureString(autofill::DialogType dialog_type) { + switch (dialog_type) { + case DIALOG_TYPE_REQUEST_AUTOCOMPLETE: + return "REQUEST_AUTOCOMPLETE"; + case DIALOG_TYPE_AUTOCHECKOUT: + return "AUTOCHECKOUT"; + } + NOTREACHED(); + return "NOT_POSSIBLE"; +} + +std::string RiskCapabilityToString( + WalletClient::RiskCapability risk_capability) { + switch (risk_capability) { + case WalletClient::RELOGIN: + return "RELOGIN"; + case WalletClient::VERIFY_CVC: + return "VERIFY_CVC"; + } + NOTREACHED(); + return "NOT_POSSIBLE"; +} + +WalletClient::ErrorType StringToErrorType(const std::string& error_type) { + std::string trimmed; + TrimWhitespaceASCII(error_type, + TRIM_ALL, + &trimmed); + if (LowerCaseEqualsASCII(trimmed, "buyer_account_error")) + return WalletClient::BUYER_ACCOUNT_ERROR; + if (LowerCaseEqualsASCII(trimmed, "internal_error")) + return WalletClient::INTERNAL_ERROR; + if (LowerCaseEqualsASCII(trimmed, "invalid_params")) + return WalletClient::INVALID_PARAMS; + if (LowerCaseEqualsASCII(trimmed, "service_unavailable")) + return WalletClient::SERVICE_UNAVAILABLE; + if (LowerCaseEqualsASCII(trimmed, "spending_limit_exceeded")) + return WalletClient::SPENDING_LIMIT_EXCEEDED; + if (LowerCaseEqualsASCII(trimmed, "unsupported_api_version")) + return WalletClient::UNSUPPORTED_API_VERSION; + return WalletClient::UNKNOWN_ERROR; +} + +// Gets and parses required actions from a SaveToWallet response. Returns +// false if any unknown required actions are seen and true otherwise. +void GetRequiredActionsForSaveToWallet( + const base::DictionaryValue& dict, + std::vector<RequiredAction>* required_actions) { + const base::ListValue* required_action_list; + if (!dict.GetList("required_action", &required_action_list)) + return; + + for (size_t i = 0; i < required_action_list->GetSize(); ++i) { + std::string action_string; + if (required_action_list->GetString(i, &action_string)) { + RequiredAction action = ParseRequiredActionFromString(action_string); + if (!ActionAppliesToSaveToWallet(action)) { + DLOG(ERROR) << "Response from Google wallet with bad required action:" + " \"" << action_string << "\""; + required_actions->clear(); + return; + } + required_actions->push_back(action); + } + } +} + +// Converts the |error_type| to the corresponding value from the stable UMA +// metric enumeration. +AutofillMetrics::WalletErrorMetric ErrorTypeToUmaMetric( + WalletClient::ErrorType error_type) { + switch (error_type) { + case WalletClient::BAD_REQUEST: + return AutofillMetrics::WALLET_BAD_REQUEST; + case WalletClient::BUYER_ACCOUNT_ERROR: + return AutofillMetrics::WALLET_BUYER_ACCOUNT_ERROR; + case WalletClient::INTERNAL_ERROR: + return AutofillMetrics::WALLET_INTERNAL_ERROR; + case WalletClient::INVALID_PARAMS: + return AutofillMetrics::WALLET_INVALID_PARAMS; + case WalletClient::SERVICE_UNAVAILABLE: + return AutofillMetrics::WALLET_SERVICE_UNAVAILABLE; + case WalletClient::SPENDING_LIMIT_EXCEEDED: + return AutofillMetrics::WALLET_SPENDING_LIMIT_EXCEEDED; + case WalletClient::UNSUPPORTED_API_VERSION: + return AutofillMetrics::WALLET_UNSUPPORTED_API_VERSION; + case WalletClient::UNKNOWN_ERROR: + return AutofillMetrics::WALLET_UNKNOWN_ERROR; + } + + NOTREACHED(); + return AutofillMetrics::WALLET_UNKNOWN_ERROR; +} + +// Converts the |required_action| to the corresponding value from the stable UMA +// metric enumeration. +AutofillMetrics::WalletRequiredActionMetric RequiredActionToUmaMetric( + RequiredAction required_action) { + switch (required_action) { + case UNKNOWN_TYPE: + return AutofillMetrics::UNKNOWN_REQUIRED_ACTION; + case CHOOSE_ANOTHER_INSTRUMENT_OR_ADDRESS: + return AutofillMetrics::CHOOSE_ANOTHER_INSTRUMENT_OR_ADDRESS; + case SETUP_WALLET: + return AutofillMetrics::SETUP_WALLET; + case ACCEPT_TOS: + return AutofillMetrics::ACCEPT_TOS; + case GAIA_AUTH: + return AutofillMetrics::GAIA_AUTH; + case UPDATE_EXPIRATION_DATE: + return AutofillMetrics::UPDATE_EXPIRATION_DATE; + case UPGRADE_MIN_ADDRESS: + return AutofillMetrics::UPGRADE_MIN_ADDRESS; + case INVALID_FORM_FIELD: + return AutofillMetrics::INVALID_FORM_FIELD; + case VERIFY_CVV: + return AutofillMetrics::VERIFY_CVV; + case PASSIVE_GAIA_AUTH: + return AutofillMetrics::PASSIVE_GAIA_AUTH; + case REQUIRE_PHONE_NUMBER: + return AutofillMetrics::REQUIRE_PHONE_NUMBER; + } + + NOTREACHED(); + return AutofillMetrics::UNKNOWN_REQUIRED_ACTION; +} + +// Keys for JSON communication with the Online Wallet server. +const char kAcceptedLegalDocumentKey[] = "accepted_legal_document"; +const char kApiKeyKey[] = "api_key"; +const char kAuthResultKey[] = "auth_result"; +const char kEncryptedOtpKey[] = "encrypted_otp"; +const char kErrorTypeKey[] = "wallet_error.error_type"; +const char kFeatureKey[] = "feature"; +const char kGoogleTransactionIdKey[] = "google_transaction_id"; +const char kInstrumentIdKey[] = "instrument_id"; +const char kInstrumentKey[] = "instrument"; +const char kInstrumentEscrowHandleKey[] = "instrument_escrow_handle"; +const char kInstrumentExpMonthKey[] = "instrument.credit_card.exp_month"; +const char kInstrumentExpYearKey[] = "instrument.credit_card.exp_year"; +const char kInstrumentType[] = "instrument.type"; +const char kInstrumentPhoneNumberKey[] = "instrument_phone_number"; +const char kMerchantDomainKey[] = "merchant_domain"; +const char kReasonKey[] = "reason"; +const char kRiskCapabilitiesKey[] = "supported_risk_challenge"; +const char kRiskParamsKey[] = "risk_params"; +const char kSelectedAddressIdKey[] = "selected_address_id"; +const char kSelectedInstrumentIdKey[] = "selected_instrument_id"; +const char kSessionMaterialKey[] = "session_material"; +const char kShippingAddressIdKey[] = "shipping_address_id"; +const char kShippingAddressKey[] = "shipping_address"; +const char kSuccessKey[] = "success"; +const char kUpgradedBillingAddressKey[] = "upgraded_billing_address"; +const char kUpgradedInstrumentIdKey[] = "upgraded_instrument_id"; + +} // namespace + +WalletClient::FullWalletRequest::FullWalletRequest( + const std::string& instrument_id, + const std::string& address_id, + const GURL& source_url, + const std::string& google_transaction_id, + const std::vector<RiskCapability> risk_capabilities) + : instrument_id(instrument_id), + address_id(address_id), + source_url(source_url), + google_transaction_id(google_transaction_id), + risk_capabilities(risk_capabilities) {} + +WalletClient::FullWalletRequest::~FullWalletRequest() {} + +WalletClient::UpdateInstrumentRequest::UpdateInstrumentRequest( + const std::string& instrument_id, + const GURL& source_url) + : instrument_id(instrument_id), + expiration_month(0), + expiration_year(0), + source_url(source_url) {} + +WalletClient::UpdateInstrumentRequest::~UpdateInstrumentRequest() {} + +WalletClient::WalletClient(net::URLRequestContextGetter* context_getter, + WalletClientDelegate* delegate) + : context_getter_(context_getter), + delegate_(delegate), + request_type_(NO_PENDING_REQUEST), + one_time_pad_(kOneTimePadLength), + encryption_escrow_client_(context_getter, this) { + DCHECK(context_getter_.get()); + DCHECK(delegate_); +} + +WalletClient::~WalletClient() {} + +void WalletClient::AcceptLegalDocuments( + const std::vector<WalletItems::LegalDocument*>& documents, + const std::string& google_transaction_id, + const GURL& source_url) { + if (documents.empty()) + return; + + std::vector<std::string> document_ids; + for (size_t i = 0; i < documents.size(); ++i) { + document_ids.push_back(documents[i]->id()); + } + DoAcceptLegalDocuments(document_ids, google_transaction_id, source_url); +} + +void WalletClient::AuthenticateInstrument( + const std::string& instrument_id, + const std::string& card_verification_number, + const std::string& obfuscated_gaia_id) { + if (HasRequestInProgress()) { + pending_requests_.push(base::Bind(&WalletClient::AuthenticateInstrument, + base::Unretained(this), + instrument_id, + card_verification_number, + obfuscated_gaia_id)); + return; + } + + DCHECK_EQ(NO_PENDING_REQUEST, request_type_); + DCHECK(pending_request_body_.empty()); + request_type_ = AUTHENTICATE_INSTRUMENT; + + pending_request_body_.SetString(kApiKeyKey, google_apis::GetAPIKey()); + pending_request_body_.SetString(kRiskParamsKey, delegate_->GetRiskData()); + pending_request_body_.SetString(kInstrumentIdKey, instrument_id); + + encryption_escrow_client_.EscrowCardVerificationNumber( + card_verification_number, obfuscated_gaia_id); +} + +void WalletClient::GetFullWallet(const FullWalletRequest& full_wallet_request) { + if (HasRequestInProgress()) { + pending_requests_.push(base::Bind(&WalletClient::GetFullWallet, + base::Unretained(this), + full_wallet_request)); + return; + } + + DCHECK_EQ(NO_PENDING_REQUEST, request_type_); + DCHECK(pending_request_body_.empty()); + request_type_ = GET_FULL_WALLET; + + pending_request_body_.SetString(kApiKeyKey, google_apis::GetAPIKey()); + pending_request_body_.SetString(kRiskParamsKey, delegate_->GetRiskData()); + pending_request_body_.SetString(kSelectedInstrumentIdKey, + full_wallet_request.instrument_id); + pending_request_body_.SetString(kSelectedAddressIdKey, + full_wallet_request.address_id); + pending_request_body_.SetString( + kMerchantDomainKey, + full_wallet_request.source_url.GetWithEmptyPath().spec()); + pending_request_body_.SetString(kGoogleTransactionIdKey, + full_wallet_request.google_transaction_id); + pending_request_body_.SetString( + kFeatureKey, + DialogTypeToFeatureString(delegate_->GetDialogType())); + + scoped_ptr<base::ListValue> risk_capabilities_list(new base::ListValue()); + for (std::vector<RiskCapability>::const_iterator it = + full_wallet_request.risk_capabilities.begin(); + it != full_wallet_request.risk_capabilities.end(); + ++it) { + risk_capabilities_list->AppendString(RiskCapabilityToString(*it)); + } + pending_request_body_.Set(kRiskCapabilitiesKey, + risk_capabilities_list.release()); + + crypto::RandBytes(&(one_time_pad_[0]), one_time_pad_.size()); + encryption_escrow_client_.EncryptOneTimePad(one_time_pad_); +} + +void WalletClient::GetWalletItems(const GURL& source_url) { + if (HasRequestInProgress()) { + pending_requests_.push(base::Bind(&WalletClient::GetWalletItems, + base::Unretained(this), + source_url)); + return; + } + + DCHECK_EQ(NO_PENDING_REQUEST, request_type_); + request_type_ = GET_WALLET_ITEMS; + + base::DictionaryValue request_dict; + request_dict.SetString(kApiKeyKey, google_apis::GetAPIKey()); + request_dict.SetString(kMerchantDomainKey, + source_url.GetWithEmptyPath().spec()); + + std::string post_body; + base::JSONWriter::Write(&request_dict, &post_body); + + MakeWalletRequest(GetGetWalletItemsUrl(), post_body); +} + +void WalletClient::SaveAddress(const Address& shipping_address, + const GURL& source_url) { + if (HasRequestInProgress()) { + pending_requests_.push(base::Bind(&WalletClient::SaveAddress, + base::Unretained(this), + shipping_address, + source_url)); + return; + } + + DCHECK_EQ(NO_PENDING_REQUEST, request_type_); + request_type_ = SAVE_ADDRESS; + + base::DictionaryValue request_dict; + request_dict.SetString(kApiKeyKey, google_apis::GetAPIKey()); + request_dict.SetString(kRiskParamsKey, delegate_->GetRiskData()); + request_dict.SetString(kMerchantDomainKey, + source_url.GetWithEmptyPath().spec()); + + request_dict.Set(kShippingAddressKey, + shipping_address.ToDictionaryWithID().release()); + + std::string post_body; + base::JSONWriter::Write(&request_dict, &post_body); + + MakeWalletRequest(GetSaveToWalletUrl(), post_body); +} + +void WalletClient::SaveInstrument( + const Instrument& instrument, + const std::string& obfuscated_gaia_id, + const GURL& source_url) { + if (HasRequestInProgress()) { + pending_requests_.push(base::Bind(&WalletClient::SaveInstrument, + base::Unretained(this), + instrument, + obfuscated_gaia_id, + source_url)); + return; + } + + DCHECK_EQ(NO_PENDING_REQUEST, request_type_); + DCHECK(pending_request_body_.empty()); + request_type_ = SAVE_INSTRUMENT; + + pending_request_body_.SetString(kApiKeyKey, google_apis::GetAPIKey()); + pending_request_body_.SetString(kRiskParamsKey, delegate_->GetRiskData()); + pending_request_body_.SetString(kMerchantDomainKey, + source_url.GetWithEmptyPath().spec()); + + pending_request_body_.Set(kInstrumentKey, + instrument.ToDictionary().release()); + pending_request_body_.SetString(kInstrumentPhoneNumberKey, + instrument.address().phone_number()); + + encryption_escrow_client_.EscrowInstrumentInformation(instrument, + obfuscated_gaia_id); +} + +void WalletClient::SaveInstrumentAndAddress( + const Instrument& instrument, + const Address& address, + const std::string& obfuscated_gaia_id, + const GURL& source_url) { + if (HasRequestInProgress()) { + pending_requests_.push(base::Bind(&WalletClient::SaveInstrumentAndAddress, + base::Unretained(this), + instrument, + address, + obfuscated_gaia_id, + source_url)); + return; + } + + DCHECK_EQ(NO_PENDING_REQUEST, request_type_); + DCHECK(pending_request_body_.empty()); + request_type_ = SAVE_INSTRUMENT_AND_ADDRESS; + + pending_request_body_.SetString(kApiKeyKey, google_apis::GetAPIKey()); + pending_request_body_.SetString(kRiskParamsKey, delegate_->GetRiskData()); + pending_request_body_.SetString(kMerchantDomainKey, + source_url.GetWithEmptyPath().spec()); + + pending_request_body_.Set(kInstrumentKey, + instrument.ToDictionary().release()); + pending_request_body_.SetString(kInstrumentPhoneNumberKey, + instrument.address().phone_number()); + + pending_request_body_.Set(kShippingAddressKey, + address.ToDictionaryWithID().release()); + + encryption_escrow_client_.EscrowInstrumentInformation(instrument, + obfuscated_gaia_id); +} + +void WalletClient::SendAutocheckoutStatus( + AutocheckoutStatus status, + const GURL& source_url, + const std::string& google_transaction_id) { + DVLOG(1) << "Sending Autocheckout Status: " << status + << " for: " << source_url; + if (HasRequestInProgress()) { + pending_requests_.push(base::Bind(&WalletClient::SendAutocheckoutStatus, + base::Unretained(this), + status, + source_url, + google_transaction_id)); + return; + } + + DCHECK_EQ(NO_PENDING_REQUEST, request_type_); + request_type_ = SEND_STATUS; + + base::DictionaryValue request_dict; + request_dict.SetString(kApiKeyKey, google_apis::GetAPIKey()); + bool success = status == SUCCESS; + request_dict.SetBoolean(kSuccessKey, success); + request_dict.SetString(kMerchantDomainKey, + source_url.GetWithEmptyPath().spec()); + if (!success) + request_dict.SetString(kReasonKey, AutocheckoutStatusToString(status)); + request_dict.SetString(kGoogleTransactionIdKey, google_transaction_id); + + std::string post_body; + base::JSONWriter::Write(&request_dict, &post_body); + + MakeWalletRequest(GetSendStatusUrl(), post_body); +} + +void WalletClient::UpdateAddress(const Address& address, + const GURL& source_url) { + if (HasRequestInProgress()) { + pending_requests_.push(base::Bind(&WalletClient::UpdateAddress, + base::Unretained(this), + address, + source_url)); + return; + } + + DCHECK_EQ(NO_PENDING_REQUEST, request_type_); + request_type_ = UPDATE_ADDRESS; + + base::DictionaryValue request_dict; + request_dict.SetString(kApiKeyKey, google_apis::GetAPIKey()); + request_dict.SetString(kRiskParamsKey, delegate_->GetRiskData()); + request_dict.SetString(kMerchantDomainKey, + source_url.GetWithEmptyPath().spec()); + + request_dict.Set(kShippingAddressKey, + address.ToDictionaryWithID().release()); + + std::string post_body; + base::JSONWriter::Write(&request_dict, &post_body); + + MakeWalletRequest(GetSaveToWalletUrl(), post_body); +} + +void WalletClient::UpdateInstrument( + const UpdateInstrumentRequest& update_instrument_request, + scoped_ptr<Address> billing_address) { + if (HasRequestInProgress()) { + pending_requests_.push(base::Bind(&WalletClient::UpdateInstrument, + base::Unretained(this), + update_instrument_request, + base::Passed(&billing_address))); + return; + } + + DCHECK_EQ(NO_PENDING_REQUEST, request_type_); + DCHECK(pending_request_body_.empty()); + DCHECK(update_instrument_request.card_verification_number.empty() == + update_instrument_request.obfuscated_gaia_id.empty()); + DCHECK(billing_address || + (update_instrument_request.expiration_month > 0 && + update_instrument_request.expiration_year > 0)); + + request_type_ = UPDATE_INSTRUMENT; + + base::DictionaryValue* active_request_body; + base::DictionaryValue request_dict; + if (update_instrument_request.card_verification_number.empty()) + active_request_body = &request_dict; + else + active_request_body = &pending_request_body_; + + active_request_body->SetString(kApiKeyKey, google_apis::GetAPIKey()); + active_request_body->SetString(kRiskParamsKey, delegate_->GetRiskData()); + active_request_body->SetString( + kMerchantDomainKey, + update_instrument_request.source_url.GetWithEmptyPath().spec()); + + active_request_body->SetString(kUpgradedInstrumentIdKey, + update_instrument_request.instrument_id); + + if (billing_address) { + active_request_body->SetString(kInstrumentPhoneNumberKey, + billing_address->phone_number()); + active_request_body->Set( + kUpgradedBillingAddressKey, + billing_address->ToDictionaryWithoutID().release()); + } + + if (update_instrument_request.expiration_month > 0 && + update_instrument_request.expiration_year > 0) { + DCHECK(!update_instrument_request.card_verification_number.empty()); + active_request_body->SetInteger( + kInstrumentExpMonthKey, + update_instrument_request.expiration_month); + active_request_body->SetInteger(kInstrumentExpYearKey, + update_instrument_request.expiration_year); + } + + if (active_request_body->HasKey(kInstrumentKey)) + active_request_body->SetString(kInstrumentType, "CREDIT_CARD"); + + if (update_instrument_request.card_verification_number.empty()) { + std::string post_body; + base::JSONWriter::Write(active_request_body, &post_body); + MakeWalletRequest(GetSaveToWalletUrl(), post_body); + } else { + encryption_escrow_client_.EscrowCardVerificationNumber( + update_instrument_request.card_verification_number, + update_instrument_request.obfuscated_gaia_id); + } +} + +bool WalletClient::HasRequestInProgress() const { + // |SaveInstrument*()| and |UpdateInstrument()| methods don't set |request_| + // until sensitive info has been escrowed, so this class is considered to have + // a request in progress if |encryption_escrow_client_| is working as well. + return request_ || encryption_escrow_client_.HasRequestInProgress(); +} + +void WalletClient::CancelRequests() { + encryption_escrow_client_.CancelRequest(); + pending_request_body_.Clear(); + request_.reset(); + request_type_ = NO_PENDING_REQUEST; + while (!pending_requests_.empty()) { + pending_requests_.pop(); + } +} + +void WalletClient::DoAcceptLegalDocuments( + const std::vector<std::string>& document_ids, + const std::string& google_transaction_id, + const GURL& source_url) { + if (HasRequestInProgress()) { + pending_requests_.push(base::Bind(&WalletClient::DoAcceptLegalDocuments, + base::Unretained(this), + document_ids, + google_transaction_id, + source_url)); + return; + } + + DCHECK_EQ(NO_PENDING_REQUEST, request_type_); + request_type_ = ACCEPT_LEGAL_DOCUMENTS; + + base::DictionaryValue request_dict; + request_dict.SetString(kApiKeyKey, google_apis::GetAPIKey()); + request_dict.SetString(kGoogleTransactionIdKey, google_transaction_id); + request_dict.SetString(kMerchantDomainKey, + source_url.GetWithEmptyPath().spec()); + scoped_ptr<base::ListValue> docs_list(new base::ListValue()); + for (std::vector<std::string>::const_iterator it = document_ids.begin(); + it != document_ids.end(); ++it) { + if (!it->empty()) + docs_list->AppendString(*it); + } + request_dict.Set(kAcceptedLegalDocumentKey, docs_list.release()); + + std::string post_body; + base::JSONWriter::Write(&request_dict, &post_body); + + MakeWalletRequest(GetAcceptLegalDocumentsUrl(), post_body); +} + +void WalletClient::MakeWalletRequest(const GURL& url, + const std::string& post_body) { + DCHECK(!HasRequestInProgress()); + + request_.reset(net::URLFetcher::Create( + 0, url, net::URLFetcher::POST, this)); + request_->SetRequestContext(context_getter_.get()); + DVLOG(1) << "Making request to " << url << " with post_body=" << post_body; + request_->SetUploadData(kJsonMimeType, post_body); + request_started_timestamp_ = base::Time::Now(); + request_->Start(); + + delegate_->GetMetricLogger().LogWalletErrorMetric( + delegate_->GetDialogType(), + AutofillMetrics::WALLET_ERROR_BASELINE_ISSUED_REQUEST); + delegate_->GetMetricLogger().LogWalletRequiredActionMetric( + delegate_->GetDialogType(), + AutofillMetrics::WALLET_REQUIRED_ACTION_BASELINE_ISSUED_REQUEST); +} + +// TODO(ahutter): Add manual retry logic if it's necessary. +void WalletClient::OnURLFetchComplete( + const net::URLFetcher* source) { + delegate_->GetMetricLogger().LogWalletApiCallDuration( + RequestTypeToUmaMetric(request_type_), + base::Time::Now() - request_started_timestamp_); + + DCHECK_EQ(source, request_.get()); + DVLOG(1) << "Got response from " << source->GetOriginalURL(); + + std::string data; + source->GetResponseAsString(&data); + DVLOG(1) << "Response body: " << data; + + scoped_ptr<base::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; + HandleWalletError(WalletClient::BAD_REQUEST); + 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: { + scoped_ptr<Value> message_value(base::JSONReader::Read(data)); + if (message_value.get() && + message_value->IsType(Value::TYPE_DICTIONARY)) { + response_dict.reset( + static_cast<base::DictionaryValue*>(message_value.release())); + } + if (response_code == net::HTTP_INTERNAL_SERVER_ERROR) { + request_type_ = NO_PENDING_REQUEST; + + std::string error_type; + if (!response_dict->GetString(kErrorTypeKey, &error_type)) { + HandleWalletError(WalletClient::UNKNOWN_ERROR); + return; + } + + HandleWalletError(StringToErrorType(error_type)); + return; + } + break; + } + + // Anything else is an error. + default: + request_type_ = NO_PENDING_REQUEST; + HandleNetworkError(response_code); + return; + } + + RequestType type = request_type_; + request_type_ = NO_PENDING_REQUEST; + + if (!(type == ACCEPT_LEGAL_DOCUMENTS || type == SEND_STATUS) && + !response_dict) { + HandleMalformedResponse(); + return; + } + + switch (type) { + case ACCEPT_LEGAL_DOCUMENTS: + delegate_->OnDidAcceptLegalDocuments(); + break; + + case AUTHENTICATE_INSTRUMENT: { + std::string auth_result; + if (response_dict->GetString(kAuthResultKey, &auth_result)) { + std::string trimmed; + TrimWhitespaceASCII(auth_result, + TRIM_ALL, + &trimmed); + delegate_->OnDidAuthenticateInstrument( + LowerCaseEqualsASCII(trimmed, "success")); + } else { + HandleMalformedResponse(); + } + break; + } + + case SEND_STATUS: + break; + + case GET_FULL_WALLET: { + scoped_ptr<FullWallet> full_wallet( + FullWallet::CreateFullWallet(*response_dict)); + if (full_wallet) { + full_wallet->set_one_time_pad(one_time_pad_); + LogRequiredActions(full_wallet->required_actions()); + delegate_->OnDidGetFullWallet(full_wallet.Pass()); + } else { + HandleMalformedResponse(); + } + break; + } + + case GET_WALLET_ITEMS: { + scoped_ptr<WalletItems> wallet_items( + WalletItems::CreateWalletItems(*response_dict)); + if (wallet_items) { + LogRequiredActions(wallet_items->required_actions()); + delegate_->OnDidGetWalletItems(wallet_items.Pass()); + } else { + HandleMalformedResponse(); + } + break; + } + + case SAVE_ADDRESS: { + std::string shipping_address_id; + std::vector<RequiredAction> required_actions; + GetRequiredActionsForSaveToWallet(*response_dict, &required_actions); + if (response_dict->GetString(kShippingAddressIdKey, + &shipping_address_id) || + !required_actions.empty()) { + LogRequiredActions(required_actions); + delegate_->OnDidSaveAddress(shipping_address_id, required_actions); + } else { + HandleMalformedResponse(); + } + break; + } + + case SAVE_INSTRUMENT: { + std::string instrument_id; + std::vector<RequiredAction> required_actions; + GetRequiredActionsForSaveToWallet(*response_dict, &required_actions); + if (response_dict->GetString(kInstrumentIdKey, &instrument_id) || + !required_actions.empty()) { + LogRequiredActions(required_actions); + delegate_->OnDidSaveInstrument(instrument_id, required_actions); + } else { + HandleMalformedResponse(); + } + break; + } + + case SAVE_INSTRUMENT_AND_ADDRESS: { + std::string instrument_id; + response_dict->GetString(kInstrumentIdKey, &instrument_id); + std::string shipping_address_id; + response_dict->GetString(kShippingAddressIdKey, + &shipping_address_id); + std::vector<RequiredAction> required_actions; + GetRequiredActionsForSaveToWallet(*response_dict, &required_actions); + if ((!instrument_id.empty() && !shipping_address_id.empty()) || + !required_actions.empty()) { + LogRequiredActions(required_actions); + delegate_->OnDidSaveInstrumentAndAddress(instrument_id, + shipping_address_id, + required_actions); + } else { + HandleMalformedResponse(); + } + break; + } + + case UPDATE_ADDRESS: { + std::string address_id; + std::vector<RequiredAction> required_actions; + GetRequiredActionsForSaveToWallet(*response_dict, &required_actions); + if (response_dict->GetString(kShippingAddressIdKey, &address_id) || + !required_actions.empty()) { + LogRequiredActions(required_actions); + delegate_->OnDidUpdateAddress(address_id, required_actions); + } else { + HandleMalformedResponse(); + } + break; + } + + case UPDATE_INSTRUMENT: { + std::string instrument_id; + std::vector<RequiredAction> required_actions; + GetRequiredActionsForSaveToWallet(*response_dict, &required_actions); + if (response_dict->GetString(kInstrumentIdKey, &instrument_id) || + !required_actions.empty()) { + LogRequiredActions(required_actions); + delegate_->OnDidUpdateInstrument(instrument_id, required_actions); + } else { + HandleMalformedResponse(); + } + break; + } + + case NO_PENDING_REQUEST: + NOTREACHED(); + } + + request_.reset(); + StartNextPendingRequest(); +} + +void WalletClient::StartNextPendingRequest() { + if (pending_requests_.empty()) + return; + + base::Closure next_request = pending_requests_.front(); + pending_requests_.pop(); + next_request.Run(); +} + +void WalletClient::HandleMalformedResponse() { + // Called to inform exponential backoff logic of the error. + request_->ReceivedContentWasMalformed(); + delegate_->OnMalformedResponse(); + + delegate_->GetMetricLogger().LogWalletErrorMetric( + delegate_->GetDialogType(), AutofillMetrics::WALLET_MALFORMED_RESPONSE); +} + +void WalletClient::HandleNetworkError(int response_code) { + delegate_->OnNetworkError(response_code); + delegate_->GetMetricLogger().LogWalletErrorMetric( + delegate_->GetDialogType(), AutofillMetrics::WALLET_NETWORK_ERROR); +} + +void WalletClient::HandleWalletError(WalletClient::ErrorType error_type) { + delegate_->OnWalletError(error_type); + delegate_->GetMetricLogger().LogWalletErrorMetric( + delegate_->GetDialogType(), ErrorTypeToUmaMetric(error_type)); +} + +void WalletClient::OnDidEncryptOneTimePad( + const std::string& encrypted_one_time_pad, + const std::string& session_material) { + DCHECK_EQ(GET_FULL_WALLET, request_type_); + pending_request_body_.SetString(kEncryptedOtpKey, encrypted_one_time_pad); + pending_request_body_.SetString(kSessionMaterialKey, session_material); + + std::string post_body; + base::JSONWriter::Write(&pending_request_body_, &post_body); + pending_request_body_.Clear(); + + MakeWalletRequest(GetGetFullWalletUrl(), post_body); +} + +void WalletClient::OnDidEscrowInstrumentInformation( + const std::string& escrow_handle) { + DCHECK(request_type_ == SAVE_INSTRUMENT || + request_type_ == SAVE_INSTRUMENT_AND_ADDRESS); + + pending_request_body_.SetString(kInstrumentEscrowHandleKey, escrow_handle); + + std::string post_body; + base::JSONWriter::Write(&pending_request_body_, &post_body); + pending_request_body_.Clear(); + + MakeWalletRequest(GetSaveToWalletUrl(), post_body); +} + +void WalletClient::OnDidEscrowCardVerificationNumber( + const std::string& escrow_handle) { + DCHECK(request_type_ == AUTHENTICATE_INSTRUMENT || + request_type_ == UPDATE_INSTRUMENT); + pending_request_body_.SetString(kInstrumentEscrowHandleKey, escrow_handle); + + std::string post_body; + base::JSONWriter::Write(&pending_request_body_, &post_body); + pending_request_body_.Clear(); + + if (request_type_ == AUTHENTICATE_INSTRUMENT) + MakeWalletRequest(GetAuthenticateInstrumentUrl(), post_body); + else + MakeWalletRequest(GetSaveToWalletUrl(), post_body); +} + +void WalletClient::OnDidMakeRequest() { + delegate_->GetMetricLogger().LogWalletErrorMetric( + delegate_->GetDialogType(), + AutofillMetrics::WALLET_ERROR_BASELINE_ISSUED_REQUEST); +} + +void WalletClient::OnNetworkError(int response_code) { + HandleNetworkError(response_code); +} + +void WalletClient::OnMalformedResponse() { + delegate_->OnMalformedResponse(); + delegate_->GetMetricLogger().LogWalletErrorMetric( + delegate_->GetDialogType(), AutofillMetrics::WALLET_MALFORMED_RESPONSE); +} + +// Logs an UMA metric for each of the |required_actions|. +void WalletClient::LogRequiredActions( + const std::vector<RequiredAction>& required_actions) const { + for (size_t i = 0; i < required_actions.size(); ++i) { + delegate_->GetMetricLogger().LogWalletRequiredActionMetric( + delegate_->GetDialogType(), + RequiredActionToUmaMetric(required_actions[i])); + } +} + +AutofillMetrics::WalletApiCallMetric WalletClient::RequestTypeToUmaMetric( + RequestType request_type) const { + switch (request_type) { + case ACCEPT_LEGAL_DOCUMENTS: + return AutofillMetrics::ACCEPT_LEGAL_DOCUMENTS; + case AUTHENTICATE_INSTRUMENT: + return AutofillMetrics::AUTHENTICATE_INSTRUMENT; + case GET_FULL_WALLET: + return AutofillMetrics::GET_FULL_WALLET; + case GET_WALLET_ITEMS: + return AutofillMetrics::GET_WALLET_ITEMS; + case SAVE_ADDRESS: + return AutofillMetrics::SAVE_ADDRESS; + case SAVE_INSTRUMENT: + return AutofillMetrics::SAVE_INSTRUMENT; + case SAVE_INSTRUMENT_AND_ADDRESS: + return AutofillMetrics::SAVE_INSTRUMENT_AND_ADDRESS; + case SEND_STATUS: + return AutofillMetrics::SEND_STATUS; + case UPDATE_ADDRESS: + return AutofillMetrics::UPDATE_ADDRESS; + case UPDATE_INSTRUMENT: + return AutofillMetrics::UPDATE_INSTRUMENT; + case NO_PENDING_REQUEST: + NOTREACHED(); + return AutofillMetrics::UNKNOWN_API_CALL; + } + + NOTREACHED(); + return AutofillMetrics::UNKNOWN_API_CALL; +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/content/browser/wallet/wallet_client.h b/components/autofill/content/browser/wallet/wallet_client.h new file mode 100644 index 0000000..13163b6 --- /dev/null +++ b/components/autofill/content/browser/wallet/wallet_client.h @@ -0,0 +1,323 @@ +// Copyright 2013 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 COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_WALLET_CLIENT_H_ +#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_WALLET_CLIENT_H_ + +#include <queue> +#include <string> +#include <vector> + +#include "base/callback.h" // For base::Closure. +#include "base/memory/ref_counted.h" +#include "base/time.h" +#include "base/values.h" +#include "components/autofill/browser/autofill_manager_delegate.h" +#include "components/autofill/browser/autofill_metrics.h" +#include "components/autofill/common/autocheckout_status.h" +#include "components/autofill/content/browser/wallet/encryption_escrow_client.h" +#include "components/autofill/content/browser/wallet/encryption_escrow_client_observer.h" +#include "components/autofill/content/browser/wallet/full_wallet.h" +#include "components/autofill/content/browser/wallet/wallet_items.h" +#include "googleurl/src/gurl.h" +#include "net/url_request/url_fetcher_delegate.h" +#include "testing/gtest/include/gtest/gtest_prod.h" + +namespace net { +class URLFetcher; +class URLRequestContextGetter; +} + +namespace autofill { +namespace wallet { + +class Address; +class FullWallet; +class Instrument; +class WalletClientDelegate; + +// WalletClient is responsible for making calls to the Online Wallet backend on +// the user's behalf. The normal flow for using this class is as follows: +// 1) GetWalletItems should be called to retrieve the user's Wallet. +// a) If the user does not have a Wallet, they must AcceptLegalDocuments and +// SaveInstrumentAndAddress before continuing. +// b) If the user has not accepted the most recent legal documents for +// Wallet, they must AcceptLegalDocuments. +// 2) The user then chooses what instrument and shipping address to use for the +// current transaction. +// a) If they choose an instrument with a zip code only address, the billing +// address will need to be updated using UpdateInstrument. +// b) The user may also choose to add a new instrument or address using +// SaveAddress, SaveInstrument, or SaveInstrumentAndAddress. +// 3) Once the user has selected the backing instrument and shipping address +// for this transaction, a FullWallet with the fronting card is generated +// using GetFullWallet. +// a) GetFullWallet may return a Risk challenge for the user. In that case, +// the user will need to verify who they are by authenticating their +// chosen backing instrument through AuthenticateInstrument +// 4) If the user initiated Autocheckout, SendAutocheckoutStatus to notify +// Online Wallet of the status flow to record various metrics. +// +// WalletClient is designed so only one request to Online Wallet can be outgoing +// at any one time. If |HasRequestInProgress()| is true while calling e.g. +// GetWalletItems(), the request will be queued and started later. Queued +// requests start in the order they were received. + +class WalletClient + : public net::URLFetcherDelegate, + public EncryptionEscrowClientObserver { + public: + // The Risk challenges supported by users of WalletClient. + enum RiskCapability { + RELOGIN, + VERIFY_CVC, + }; + + // The type of error returned by Online Wallet. + enum ErrorType { + // Errors to display to users. + BUYER_ACCOUNT_ERROR, // Risk deny, unsupported country, or account + // closed. + SPENDING_LIMIT_EXCEEDED, // User needs make a cheaper transaction or not + // use Online Wallet. + + // API errors. + BAD_REQUEST, // Request was very malformed or sent to the + // wrong endpoint. + INVALID_PARAMS, // API call had missing or invalid parameters. + UNSUPPORTED_API_VERSION, // The server API version of the request is no + // longer supported. + + // Server errors. + INTERNAL_ERROR, // Unknown server side error. + SERVICE_UNAVAILABLE, // Online Wallet is down. + + UNKNOWN_ERROR, // Catch all error type. + }; + + struct FullWalletRequest { + public: + FullWalletRequest(const std::string& instrument_id, + const std::string& address_id, + const GURL& source_url, + const std::string& google_transaction_id, + const std::vector<RiskCapability> risk_capabilities); + ~FullWalletRequest(); + + // The ID of the backing instrument. Should have been selected by the user + // in some UI. + std::string instrument_id; + + // The ID of the shipping address. Should have been selected by the user + // in some UI. + std::string address_id; + + // The URL that Online Wallet usage is being initiated on. + GURL source_url; + + // The transaction ID from GetWalletItems. + std::string google_transaction_id; + + // The Risk challenges supported by the user of WalletClient + std::vector<RiskCapability> risk_capabilities; + + private: + DISALLOW_ASSIGN(FullWalletRequest); + }; + + struct UpdateInstrumentRequest { + public: + UpdateInstrumentRequest(const std::string& instrument_id, + const GURL& source_url); + ~UpdateInstrumentRequest(); + + // The id of the instrument being modified. + std::string instrument_id; + + // The new expiration date. If these are set, |card_verification_number| and + // |obfuscated_gaia_id| must be provided. + int expiration_month; + int expiration_year; + + // Used to authenticate the card the user is modifying. + std::string card_verification_number; + + // Used to key the escrow of |card_verification_number|. + std::string obfuscated_gaia_id; + + // The url this call is initiated from. + GURL source_url; + + private: + DISALLOW_ASSIGN(UpdateInstrumentRequest); + }; + + // |context_getter| is reference counted so it has no lifetime or ownership + // requirements. |delegate| must outlive |this|. + WalletClient(net::URLRequestContextGetter* context_getter, + WalletClientDelegate* delegate); + + 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. + virtual void GetWalletItems(const GURL& source_url); + + // The GetWalletItems call to the Online Wallet backend may require the user + // to accept various legal documents before a FullWallet can be generated. + // The |google_transaction_id| is provided in the response to the + // GetWalletItems call. + virtual void AcceptLegalDocuments( + const std::vector<WalletItems::LegalDocument*>& documents, + const std::string& google_transaction_id, + const GURL& source_url); + + // Authenticates that |card_verification_number| is for the backing instrument + // with |instrument_id|. |obfuscated_gaia_id| is used as a key when escrowing + // |card_verification_number|. |delegate_| is notified when the request is + // complete. Used to respond to Risk challenges. + virtual void AuthenticateInstrument( + const std::string& instrument_id, + const std::string& card_verification_number, + const std::string& obfuscated_gaia_id); + + // GetFullWallet retrieves the a FullWallet for the user. + virtual void GetFullWallet(const FullWalletRequest& full_wallet_request); + + // SaveAddress saves a new shipping address. + virtual void SaveAddress(const Address& address, const GURL& source_url); + + // SaveInstrument saves a new instrument. + virtual void SaveInstrument(const Instrument& instrument, + const std::string& obfuscated_gaia_id, + const GURL& source_url); + + // SaveInstrumentAndAddress saves a new instrument and address. + virtual void SaveInstrumentAndAddress(const Instrument& instrument, + const Address& shipping_address, + const std::string& obfuscated_gaia_id, + const GURL& source_url); + + // SendAutocheckoutStatus is used for tracking the success of Autocheckout + // flows. |status| is the result of the flow, |merchant_domain| is the domain + // where the purchase occured, and |google_transaction_id| is the same as the + // one provided by GetWalletItems. + void SendAutocheckoutStatus(autofill::AutocheckoutStatus status, + const GURL& source_url, + const std::string& google_transaction_id); + + // UpdateAddress updates Online Wallet with the data in |address|. + virtual void UpdateAddress(const Address& address, const GURL& source_url); + + // Updates Online Wallet with the data in |update_instrument_request| and, if + // it's provided, |billing_address|. + virtual void UpdateInstrument( + const UpdateInstrumentRequest& update_instrument_request, + scoped_ptr<Address> billing_address); + + bool HasRequestInProgress() const; + + // Cancels and clears the current |request_| and |pending_requests_| (if any). + void CancelRequests(); + + private: + FRIEND_TEST_ALL_PREFIXES(WalletClientTest, PendingRequest); + FRIEND_TEST_ALL_PREFIXES(WalletClientTest, CancelRequests); + + enum RequestType { + NO_PENDING_REQUEST, + ACCEPT_LEGAL_DOCUMENTS, + AUTHENTICATE_INSTRUMENT, + GET_FULL_WALLET, + GET_WALLET_ITEMS, + SAVE_ADDRESS, + SAVE_INSTRUMENT, + SAVE_INSTRUMENT_AND_ADDRESS, + SEND_STATUS, + UPDATE_ADDRESS, + UPDATE_INSTRUMENT, + }; + + // Like AcceptLegalDocuments, but takes a vector of document ids. + void DoAcceptLegalDocuments( + const std::vector<std::string>& document_ids, + const std::string& google_transaction_id, + const GURL& source_url); + + // Posts |post_body| to |url| and notifies |delegate_| when the request is + // complete. + void MakeWalletRequest(const GURL& url, const std::string& post_body); + + // Performs bookkeeping tasks for any invalid requests. + void HandleMalformedResponse(); + void HandleNetworkError(int response_code); + void HandleWalletError(ErrorType error_type); + + // Start the next pending request (if any). + void StartNextPendingRequest(); + + // net::URLFetcherDelegate: + virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE; + + // EncryptionEscrowClientObserver: + virtual void OnDidEncryptOneTimePad( + const std::string& encrypted_one_time_pad, + const std::string& session_material) OVERRIDE; + virtual void OnDidEscrowInstrumentInformation( + const std::string& escrow_handle) OVERRIDE; + virtual void OnDidEscrowCardVerificationNumber( + const std::string& escrow_handle) OVERRIDE; + virtual void OnDidMakeRequest() OVERRIDE; + virtual void OnNetworkError(int response_code) OVERRIDE; + virtual void OnMalformedResponse() OVERRIDE; + + // Logs an UMA metric for each of the |required_actions|. + void LogRequiredActions( + const std::vector<RequiredAction>& required_actions) const; + + // Converts |request_type| to an UMA metric. + AutofillMetrics::WalletApiCallMetric RequestTypeToUmaMetric( + RequestType request_type) const; + + // 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. + WalletClientDelegate* const delegate_; // must outlive |this|. + + // 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_; + + // The one time pad used for GetFullWallet encryption. + std::vector<uint8> one_time_pad_; + + // GetFullWallet requests and requests that alter instruments rely on requests + // made through the |encryption_escrow_client_| finishing first. The request + // body is saved here while that those requests are in flight. + base::DictionaryValue pending_request_body_; + + // Requests that are waiting to be run. + std::queue<base::Closure> pending_requests_; + + // This client is repsonsible for making encryption and escrow calls to Online + // Wallet. + EncryptionEscrowClient encryption_escrow_client_; + + // When the current request started. Used to track client side latency. + base::Time request_started_timestamp_; + + DISALLOW_COPY_AND_ASSIGN(WalletClient); +}; + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_WALLET_CLIENT_H_ diff --git a/components/autofill/content/browser/wallet/wallet_client_delegate.h b/components/autofill/content/browser/wallet/wallet_client_delegate.h new file mode 100644 index 0000000..895a265 --- /dev/null +++ b/components/autofill/content/browser/wallet/wallet_client_delegate.h @@ -0,0 +1,112 @@ +// Copyright 2013 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 COMPONENTS_AUTOFILL_BROWSER_WALLET_WALLET_CLIENT_OBSERVER_H_ +#define COMPONENTS_AUTOFILL_BROWSER_WALLET_WALLET_CLIENT_OBSERVER_H_ + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "components/autofill/browser/autofill_manager_delegate.h" +#include "components/autofill/content/browser/wallet/wallet_client.h" + +class AutofillMetrics; + +namespace autofill { +namespace wallet { + +class FullWallet; +class WalletItems; + +// WalletClientDelegate is to be implemented any classes making calls with +// WalletClient. The appropriate callback method will be called on +// WalletClientDelegate with the response from the Online Wallet backend. +class WalletClientDelegate { + public: + // -------------------------------------- + // Accessors called when making requests. + // -------------------------------------- + + // Returns the MetricLogger instance that should be used for logging Online + // Wallet metrics. + virtual const AutofillMetrics& GetMetricLogger() const = 0; + + // Returns the dialog type that the delegate corresponds to. + virtual DialogType GetDialogType() const = 0; + + // Returns the serialized fingerprint data to be sent to the Risk server. + virtual std::string GetRiskData() const = 0; + + // -------------------------------------------------------------------------- + // Callbacks called with responses from the Online Wallet backend. + // -------------------------------------------------------------------------- + + // Called when an AcceptLegalDocuments request finishes successfully. + virtual void OnDidAcceptLegalDocuments() = 0; + + // Called when an AuthenticateInstrument request finishes successfully. + virtual void OnDidAuthenticateInstrument(bool success) = 0; + + // Called when a GetFullWallet request finishes successfully. Ownership is + // transferred to implementer of this interface. + virtual void OnDidGetFullWallet(scoped_ptr<FullWallet> full_wallet) = 0; + + // Called when a GetWalletItems request finishes successfully. Ownership is + // transferred to implementer of this interface. + virtual void OnDidGetWalletItems(scoped_ptr<WalletItems> wallet_items) = 0; + + // Called when a SaveAddress request finishes successfully. |address_id| can + // be used in subsequent GetFullWallet calls. |required_actions| is populated + // if there was a validation error with the data being saved. + virtual void OnDidSaveAddress( + const std::string& address_id, + const std::vector<RequiredAction>& required_actions) = 0; + + // Called when a SaveInstrument request finishes sucessfully. |instrument_id| + // can be used in subsequent GetFullWallet calls. |required_actions| is + // populated if there was a validation error with the data being saved. + virtual void OnDidSaveInstrument( + const std::string& instrument_id, + const std::vector<RequiredAction>& required_actions) = 0; + + // Called when a SaveInstrumentAndAddress request finishes succesfully. + // |instrument_id| and |address_id| can be used in subsequent + // GetFullWallet calls. |required_actions| is populated if there was a + // validation error with the data being saved. + virtual void OnDidSaveInstrumentAndAddress( + const std::string& instrument_id, + const std::string& address_id, + const std::vector<RequiredAction>& required_actions) = 0; + + // Called when an UpdateAddress request finishes successfully. + // |required_actions| is populated if there was a validation error with the + // data being saved. + virtual void OnDidUpdateAddress( + const std::string& address_id, + const std::vector<RequiredAction>& required_actions) = 0; + + // Called when an UpdateInstrument request finishes successfully. + // |required_actions| is populated if there was a validation error with the + // data being saved. + virtual void OnDidUpdateInstrument( + const std::string& instrument_id, + const std::vector<RequiredAction>& required_actions) = 0; + + // Called when a request fails due to an Online Wallet error. + virtual void OnWalletError(WalletClient::ErrorType error_type) = 0; + + // Called when a request fails due to a malformed response. + virtual void OnMalformedResponse() = 0; + + // Called when a request fails due to a network error. + virtual void OnNetworkError(int response_code) = 0; + + protected: + virtual ~WalletClientDelegate() {} +}; + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_BROWSER_WALLET_WALLET_CLIENT_OBSERVER_H_ diff --git a/components/autofill/content/browser/wallet/wallet_client_unittest.cc b/components/autofill/content/browser/wallet/wallet_client_unittest.cc new file mode 100644 index 0000000..2ddc815 --- /dev/null +++ b/components/autofill/content/browser/wallet/wallet_client_unittest.cc @@ -0,0 +1,1744 @@ +// Copyright 2013 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/json/json_writer.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/string_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/values.h" +#include "chrome/test/base/testing_profile.h" +#include "components/autofill/browser/autofill_metrics.h" +#include "components/autofill/common/autocheckout_status.h" +#include "components/autofill/content/browser/wallet/full_wallet.h" +#include "components/autofill/content/browser/wallet/instrument.h" +#include "components/autofill/content/browser/wallet/wallet_client.h" +#include "components/autofill/content/browser/wallet/wallet_client_delegate.h" +#include "components/autofill/content/browser/wallet/wallet_items.h" +#include "components/autofill/content/browser/wallet/wallet_test_util.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 autofill { +namespace wallet { + +namespace { + +const char kGoogleTransactionId[] = "google-transaction-id"; +const char kMerchantUrl[] = "https://example.com/path?key=value"; + +const char kGetFullWalletValidResponse[] = + "{" + " \"expiration_month\":12," + " \"expiration_year\":3000," + " \"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\":\"US\"" + " }" + " }," + " \"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\":\"US\"" + " }" + " }," + " \"required_action\":" + " [" + " ]" + "}"; + +const char kGetFullWalletInvalidResponse[] = + "{" + " \"garbage\":123" + "}"; + +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\":\"4111\"," + " \"expiration_month\":12," + " \"expiration_year\":3000," + " \"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\"," + " \"obfuscated_gaia_id\":\"obfuscated_gaia_id\"," + " \"address\":" + " [" + " ]," + " \"default_address_id\":\"default_address_id\"," + " \"required_legal_document\":" + " [" + " ]" + "}"; + +const char kSaveAddressValidResponse[] = + "{" + " \"shipping_address_id\":\"saved_address_id\"" + "}"; + +const char kSaveAddressWithRequiredActionsValidResponse[] = + "{" + " \"required_action\":" + " [" + " \" \\treqUIRE_PhOnE_number \\n\\r\"," + " \"INVALID_form_field\"" + " ]" + "}"; + +const char kSaveWithInvalidRequiredActionsResponse[] = + "{" + " \"required_action\":" + " [" + " \" setup_wallet\"," + " \" \\treqUIRE_PhOnE_number \\n\\r\"," + " \"INVALID_form_field\"" + " ]" + "}"; + +const char kSaveInvalidResponse[] = + "{" + " \"garbage\":123" + "}"; + +const char kSaveInstrumentValidResponse[] = + "{" + " \"instrument_id\":\"instrument_id\"" + "}"; + +const char kSaveInstrumentWithRequiredActionsValidResponse[] = + "{" + " \"required_action\":" + " [" + " \" \\treqUIRE_PhOnE_number \\n\\r\"," + " \"INVALID_form_field\"" + " ]" + "}"; + +const char kSaveInstrumentAndAddressValidResponse[] = + "{" + " \"shipping_address_id\":\"saved_address_id\"," + " \"instrument_id\":\"saved_instrument_id\"" + "}"; + +const char kSaveInstrumentAndAddressWithRequiredActionsValidResponse[] = + "{" + " \"required_action\":" + " [" + " \" \\treqUIRE_PhOnE_number \\n\\r\"," + " \"INVALID_form_field\"" + " ]" + "}"; + +const char kSaveInstrumentAndAddressMissingAddressResponse[] = + "{" + " \"instrument_id\":\"instrument_id\"" + "}"; + +const char kSaveInstrumentAndAddressMissingInstrumentResponse[] = + "{" + " \"shipping_address_id\":\"saved_address_id\"" + "}"; + +const char kUpdateInstrumentValidResponse[] = + "{" + " \"instrument_id\":\"instrument_id\"" + "}"; + +const char kUpdateAddressValidResponse[] = + "{" + " \"shipping_address_id\":\"shipping_address_id\"" + "}"; + +const char kUpdateWithRequiredActionsValidResponse[] = + "{" + " \"required_action\":" + " [" + " \" \\treqUIRE_PhOnE_number \\n\\r\"," + " \"INVALID_form_field\"" + " ]" + "}"; + +const char kUpdateMalformedResponse[] = + "{" + " \"cheese\":\"monkeys\"" + "}"; + +const char kAuthenticateInstrumentFailureResponse[] = + "{" + " \"auth_result\":\"anything else\"" + "}"; + +const char kAuthenticateInstrumentSuccessResponse[] = + "{" + " \"auth_result\":\"SUCCESS\"" + "}"; + +const char kErrorResponse[] = + "{" + " \"error_type\":\"APPLICATION_ERROR\"," + " \"error_detail\":\"error_detail\"," + " \"application_error\":\"application_error\"," + " \"debug_data\":" + " {" + " \"debug_message\":\"debug_message\"," + " \"stack_trace\":\"stack_trace\"" + " }," + " \"application_error_data\":\"application_error_data\"," + " \"wallet_error\":" + " {" + " \"error_type\":\"SERVICE_UNAVAILABLE\"," + " \"error_detail\":\"error_detail\"," + " \"message_for_user\":" + " {" + " \"text\":\"text\"," + " \"subtext\":\"subtext\"," + " \"details\":\"details\"" + " }" + " }" + "}"; + +const char kErrorTypeMissingInResponse[] = + "{" + " \"error_type\":\"Not APPLICATION_ERROR\"," + " \"error_detail\":\"error_detail\"," + " \"application_error\":\"application_error\"," + " \"debug_data\":" + " {" + " \"debug_message\":\"debug_message\"," + " \"stack_trace\":\"stack_trace\"" + " }," + " \"application_error_data\":\"application_error_data\"" + "}"; + +// 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_id_1\"," + "\"doc_id_2\"" + "]," + "\"google_transaction_id\":\"google-transaction-id\"," + "\"merchant_domain\":\"https://example.com/\"" + "}"; + +const char kAuthenticateInstrumentValidRequest[] = + "{" + "\"instrument_escrow_handle\":\"escrow_handle\"," + "\"instrument_id\":\"instrument_id\"," + "\"risk_params\":\"risky business\"" + "}"; + +const char kGetFullWalletValidRequest[] = + "{" + "\"encrypted_otp\":\"encrypted_one_time_pad\"," + "\"feature\":\"REQUEST_AUTOCOMPLETE\"," + "\"google_transaction_id\":\"google_transaction_id\"," + "\"merchant_domain\":\"https://example.com/\"," + "\"risk_params\":\"risky business\"," + "\"selected_address_id\":\"shipping_address_id\"," + "\"selected_instrument_id\":\"instrument_id\"," + "\"session_material\":\"session_material\"," + "\"supported_risk_challenge\":" + "[" + "]" + "}"; + +const char kGetFullWalletWithRiskCapabilitesValidRequest[] = + "{" + "\"encrypted_otp\":\"encrypted_one_time_pad\"," + "\"feature\":\"REQUEST_AUTOCOMPLETE\"," + "\"google_transaction_id\":\"google_transaction_id\"," + "\"merchant_domain\":\"https://example.com/\"," + "\"risk_params\":\"risky business\"," + "\"selected_address_id\":\"shipping_address_id\"," + "\"selected_instrument_id\":\"instrument_id\"," + "\"session_material\":\"session_material\"," + "\"supported_risk_challenge\":" + "[" + "\"VERIFY_CVC\"" + "]" + "}"; + +const char kGetWalletItemsValidRequest[] = + "{" + "\"merchant_domain\":\"https://example.com/\"" + "}"; + +const char kSaveAddressValidRequest[] = + "{" + "\"merchant_domain\":\"https://example.com/\"," + "\"risk_params\":\"risky business\"," + "\"shipping_address\":" + "{" + "\"phone_number\":\"save_phone_number\"," + "\"postal_address\":" + "{" + "\"address_line\":" + "[" + "\"save_address_line_1\"," + "\"save_address_line_2\"" + "]," + "\"administrative_area_name\":\"save_admin_area_name\"," + "\"country_name_code\":\"US\"," + "\"locality_name\":\"save_locality_name\"," + "\"postal_code_number\":\"save_postal_code_number\"," + "\"recipient_name\":\"save_recipient_name\"" + "}" + "}" + "}"; + +const char kSaveInstrumentValidRequest[] = + "{" + "\"instrument\":" + "{" + "\"credit_card\":" + "{" + "\"address\":" + "{" + "\"address_line\":" + "[" + "\"address_line_1\"," + "\"address_line_2\"" + "]," + "\"administrative_area_name\":\"admin_area_name\"," + "\"country_name_code\":\"US\"," + "\"locality_name\":\"locality_name\"," + "\"postal_code_number\":\"postal_code_number\"," + "\"recipient_name\":\"recipient_name\"" + "}," + "\"exp_month\":12," + "\"exp_year\":3000," + "\"fop_type\":\"VISA\"," + "\"last_4_digits\":\"4448\"" + "}," + "\"type\":\"CREDIT_CARD\"" + "}," + "\"instrument_escrow_handle\":\"escrow_handle\"," + "\"instrument_phone_number\":\"phone_number\"," + "\"merchant_domain\":\"https://example.com/\"," + "\"risk_params\":\"risky business\"" + "}"; + +const char kSaveInstrumentAndAddressValidRequest[] = + "{" + "\"instrument\":" + "{" + "\"credit_card\":" + "{" + "\"address\":" + "{" + "\"address_line\":" + "[" + "\"address_line_1\"," + "\"address_line_2\"" + "]," + "\"administrative_area_name\":\"admin_area_name\"," + "\"country_name_code\":\"US\"," + "\"locality_name\":\"locality_name\"," + "\"postal_code_number\":\"postal_code_number\"," + "\"recipient_name\":\"recipient_name\"" + "}," + "\"exp_month\":12," + "\"exp_year\":3000," + "\"fop_type\":\"VISA\"," + "\"last_4_digits\":\"4448\"" + "}," + "\"type\":\"CREDIT_CARD\"" + "}," + "\"instrument_escrow_handle\":\"escrow_handle\"," + "\"instrument_phone_number\":\"phone_number\"," + "\"merchant_domain\":\"https://example.com/\"," + "\"risk_params\":\"risky business\"," + "\"shipping_address\":" + "{" + "\"phone_number\":\"save_phone_number\"," + "\"postal_address\":" + "{" + "\"address_line\":" + "[" + "\"save_address_line_1\"," + "\"save_address_line_2\"" + "]," + "\"administrative_area_name\":\"save_admin_area_name\"," + "\"country_name_code\":\"US\"," + "\"locality_name\":\"save_locality_name\"," + "\"postal_code_number\":\"save_postal_code_number\"," + "\"recipient_name\":\"save_recipient_name\"" + "}" + "}" + "}"; + +const char kSendAutocheckoutStatusOfSuccessValidRequest[] = + "{" + "\"google_transaction_id\":\"google_transaction_id\"," + "\"merchant_domain\":\"https://example.com/\"," + "\"success\":true" + "}"; + +const char kSendAutocheckoutStatusOfFailureValidRequest[] = + "{" + "\"google_transaction_id\":\"google_transaction_id\"," + "\"merchant_domain\":\"https://example.com/\"," + "\"reason\":\"CANNOT_PROCEED\"," + "\"success\":false" + "}"; + +const char kUpdateAddressValidRequest[] = + "{" + "\"merchant_domain\":\"https://example.com/\"," + "\"risk_params\":\"risky business\"," + "\"shipping_address\":" + "{" + "\"id\":\"shipping_address_id\"," + "\"phone_number\":\"ship_phone_number\"," + "\"postal_address\":" + "{" + "\"address_line\":" + "[" + "\"ship_address_line_1\"," + "\"ship_address_line_2\"" + "]," + "\"administrative_area_name\":\"ship_admin_area_name\"," + "\"country_name_code\":\"US\"," + "\"locality_name\":\"ship_locality_name\"," + "\"postal_code_number\":\"ship_postal_code_number\"," + "\"recipient_name\":\"ship_recipient_name\"" + "}" + "}" + "}"; + +const char kUpdateInstrumentAddressValidRequest[] = + "{" + "\"instrument_phone_number\":\"phone_number\"," + "\"merchant_domain\":\"https://example.com/\"," + "\"risk_params\":\"risky business\"," + "\"upgraded_billing_address\":" + "{" + "\"address_line\":" + "[" + "\"address_line_1\"," + "\"address_line_2\"" + "]," + "\"administrative_area_name\":\"admin_area_name\"," + "\"country_name_code\":\"US\"," + "\"locality_name\":\"locality_name\"," + "\"postal_code_number\":\"postal_code_number\"," + "\"recipient_name\":\"recipient_name\"" + "}," + "\"upgraded_instrument_id\":\"instrument_id\"" + "}"; + +const char kUpdateInstrumentAddressWithNameChangeValidRequest[] = + "{" + "\"instrument_escrow_handle\":\"escrow_handle\"," + "\"instrument_phone_number\":\"phone_number\"," + "\"merchant_domain\":\"https://example.com/\"," + "\"risk_params\":\"risky business\"," + "\"upgraded_billing_address\":" + "{" + "\"address_line\":" + "[" + "\"address_line_1\"," + "\"address_line_2\"" + "]," + "\"administrative_area_name\":\"admin_area_name\"," + "\"country_name_code\":\"US\"," + "\"locality_name\":\"locality_name\"," + "\"postal_code_number\":\"postal_code_number\"," + "\"recipient_name\":\"recipient_name\"" + "}," + "\"upgraded_instrument_id\":\"instrument_id\"" + "}"; + +const char kUpdateInstrumentAddressAndExpirationDateValidRequest[] = + "{" + "\"instrument\":" + "{" + "\"credit_card\":" + "{" + "\"exp_month\":12," + "\"exp_year\":2015" + "}," + "\"type\":\"CREDIT_CARD\"" + "}," + "\"instrument_escrow_handle\":\"escrow_handle\"," + "\"instrument_phone_number\":\"phone_number\"," + "\"merchant_domain\":\"https://example.com/\"," + "\"risk_params\":\"risky business\"," + "\"upgraded_billing_address\":" + "{" + "\"address_line\":" + "[" + "\"address_line_1\"," + "\"address_line_2\"" + "]," + "\"administrative_area_name\":\"admin_area_name\"," + "\"country_name_code\":\"US\"," + "\"locality_name\":\"locality_name\"," + "\"postal_code_number\":\"postal_code_number\"," + "\"recipient_name\":\"recipient_name\"" + "}," + "\"upgraded_instrument_id\":\"instrument_id\"" + "}"; + +const char kUpdateInstrumentExpirationDateValidRequest[] = + "{" + "\"instrument\":" + "{" + "\"credit_card\":" + "{" + "\"exp_month\":12," + "\"exp_year\":2015" + "}," + "\"type\":\"CREDIT_CARD\"" + "}," + "\"instrument_escrow_handle\":\"escrow_handle\"," + "\"merchant_domain\":\"https://example.com/\"," + "\"risk_params\":\"risky business\"," + "\"upgraded_instrument_id\":\"instrument_id\"" + "}"; + +class MockAutofillMetrics : public AutofillMetrics { + public: + MockAutofillMetrics() {} + MOCK_CONST_METHOD2(LogWalletApiCallDuration, + void(WalletApiCallMetric metric, + const base::TimeDelta& duration)); + MOCK_CONST_METHOD2(LogWalletErrorMetric, + void(DialogType dialog_type, WalletErrorMetric metric)); + MOCK_CONST_METHOD2(LogWalletRequiredActionMetric, + void(DialogType dialog_type, + WalletRequiredActionMetric action)); + private: + DISALLOW_COPY_AND_ASSIGN(MockAutofillMetrics); +}; + +enum EscrowRequestPresence { + HAS_ESCROW_REQUEST, + NO_ESCROW_REQUEST, +}; + +enum WalletRequestPresence { + HAS_WALLET_REQUEST, + NO_WALLET_REQUEST, +}; + +class MockWalletClientDelegate : public WalletClientDelegate { + public: + MockWalletClientDelegate() + : full_wallets_received_(0), wallet_items_received_(0) {} + ~MockWalletClientDelegate() {} + + virtual const AutofillMetrics& GetMetricLogger() const OVERRIDE { + return metric_logger_; + } + + virtual DialogType GetDialogType() const OVERRIDE { + return DIALOG_TYPE_REQUEST_AUTOCOMPLETE; + } + + virtual std::string GetRiskData() const OVERRIDE { + return "risky business"; + } + + void ExpectLogWalletApiCallDuration( + AutofillMetrics::WalletApiCallMetric metric, + size_t times) { + EXPECT_CALL(metric_logger_, + LogWalletApiCallDuration(metric, testing::_)).Times(times); + } + + void ExpectWalletErrorMetric(AutofillMetrics::WalletErrorMetric metric) { + EXPECT_CALL( + metric_logger_, + LogWalletErrorMetric( + DIALOG_TYPE_REQUEST_AUTOCOMPLETE, metric)).Times(1); + } + + void ExpectWalletRequiredActionMetric( + AutofillMetrics::WalletRequiredActionMetric metric) { + EXPECT_CALL( + metric_logger_, + LogWalletRequiredActionMetric( + DIALOG_TYPE_REQUEST_AUTOCOMPLETE, metric)).Times(1); + } + + void ExpectBaselineMetrics(EscrowRequestPresence escrow_request_presence, + WalletRequestPresence wallet_request_presence) { + int num_requests = 0; + if (escrow_request_presence == HAS_ESCROW_REQUEST) + ++num_requests; + if (wallet_request_presence == HAS_WALLET_REQUEST) + ++num_requests; + + EXPECT_GT(num_requests, 0); + + EXPECT_CALL( + metric_logger_, + LogWalletErrorMetric( + DIALOG_TYPE_REQUEST_AUTOCOMPLETE, + AutofillMetrics::WALLET_ERROR_BASELINE_ISSUED_REQUEST)) + .Times(num_requests); + + if (wallet_request_presence == HAS_WALLET_REQUEST) { + ExpectWalletRequiredActionMetric( + AutofillMetrics::WALLET_REQUIRED_ACTION_BASELINE_ISSUED_REQUEST); + } + } + + MockAutofillMetrics* metric_logger() { + return &metric_logger_; + } + + MOCK_METHOD0(OnDidAcceptLegalDocuments, void()); + MOCK_METHOD1(OnDidAuthenticateInstrument, void(bool success)); + MOCK_METHOD2(OnDidSaveAddress, + void(const std::string& address_id, + const std::vector<RequiredAction>& required_actions)); + MOCK_METHOD2(OnDidSaveInstrument, + void(const std::string& instrument_id, + const std::vector<RequiredAction>& required_actions)); + MOCK_METHOD3(OnDidSaveInstrumentAndAddress, + void(const std::string& instrument_id, + const std::string& shipping_address_id, + const std::vector<RequiredAction>& required_actions)); + MOCK_METHOD2(OnDidUpdateAddress, + void(const std::string& address_id, + const std::vector<RequiredAction>& required_actions)); + MOCK_METHOD2(OnDidUpdateInstrument, + void(const std::string& instrument_id, + const std::vector<RequiredAction>& required_actions)); + MOCK_METHOD1(OnWalletError, void(WalletClient::ErrorType error_type)); + MOCK_METHOD0(OnMalformedResponse, void()); + MOCK_METHOD1(OnNetworkError, void(int response_code)); + + virtual void OnDidGetFullWallet(scoped_ptr<FullWallet> full_wallet) OVERRIDE { + EXPECT_TRUE(full_wallet); + ++full_wallets_received_; + } + virtual void OnDidGetWalletItems(scoped_ptr<WalletItems> wallet_items) + OVERRIDE { + EXPECT_TRUE(wallet_items); + ++wallet_items_received_; + } + size_t full_wallets_received() const { return full_wallets_received_; } + size_t wallet_items_received() const { return wallet_items_received_; } + + private: + size_t full_wallets_received_; + size_t wallet_items_received_; + + testing::StrictMock<MockAutofillMetrics> metric_logger_; +}; + +} // namespace + +class WalletClientTest : public testing::Test { + public: + WalletClientTest() : io_thread_(content::BrowserThread::IO) {} + + virtual void SetUp() OVERRIDE { + io_thread_.StartIOThread(); + profile_.CreateRequestContext(); + wallet_client_.reset( + new WalletClient(profile_.GetRequestContext(), &delegate_)); + } + + virtual void TearDown() OVERRIDE { + wallet_client_.reset(); + profile_.ResetRequestContext(); + io_thread_.Stop(); + } + + std::string GetData(net::TestURLFetcher* fetcher) { + std::string data = fetcher->upload_data(); + scoped_ptr<Value> root(base::JSONReader::Read(data)); + + // If this is not a JSON dictionary, return plain text. + if (root.get() == NULL || !root->IsType(Value::TYPE_DICTIONARY)) + return data; + + // Remove api_key entry (to prevent accidental leak), return JSON as text. + DictionaryValue* dict = static_cast<DictionaryValue*>(root.get()); + dict->Remove("api_key", NULL); + base::JSONWriter::Write(dict, &data); + return data; + } + + void DoEncryptionOrEscrowRequest(net::HttpStatusCode response_code, + const std::string& response_body) { + net::TestURLFetcher* encryption_fetcher = factory_.GetFetcherByID(1); + ASSERT_TRUE(encryption_fetcher); + encryption_fetcher->set_response_code(response_code); + encryption_fetcher->SetResponseString(response_body); + encryption_fetcher->delegate()->OnURLFetchComplete(encryption_fetcher); + } + + void VerifyAndFinishRequest(net::HttpStatusCode response_code, + const std::string& request_body, + const std::string& response_body) { + net::TestURLFetcher* fetcher = factory_.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + EXPECT_EQ(request_body, GetData(fetcher)); + fetcher->set_response_code(response_code); + fetcher->SetResponseString(response_body); + fetcher->delegate()->OnURLFetchComplete(fetcher); + } + + protected: + scoped_ptr<WalletClient> wallet_client_; + MockWalletClientDelegate delegate_; + + private: + // The profile's request context must be released on the IO thread. + content::TestBrowserThread io_thread_; + TestingProfile profile_; + net::TestURLFetcherFactory factory_; +}; + +TEST_F(WalletClientTest, WalletError) { + EXPECT_CALL(delegate_, OnWalletError( + WalletClient::SERVICE_UNAVAILABLE)).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SEND_STATUS, 1); + delegate_.ExpectBaselineMetrics(NO_ESCROW_REQUEST, HAS_WALLET_REQUEST); + delegate_.ExpectWalletErrorMetric( + AutofillMetrics::WALLET_SERVICE_UNAVAILABLE); + + wallet_client_->SendAutocheckoutStatus(autofill::SUCCESS, + GURL(kMerchantUrl), + "google_transaction_id"); + VerifyAndFinishRequest(net::HTTP_INTERNAL_SERVER_ERROR, + kSendAutocheckoutStatusOfSuccessValidRequest, + kErrorResponse); +} + +TEST_F(WalletClientTest, WalletErrorResponseMissing) { + EXPECT_CALL(delegate_, OnWalletError( + WalletClient::UNKNOWN_ERROR)).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SEND_STATUS, 1); + delegate_.ExpectBaselineMetrics(NO_ESCROW_REQUEST, HAS_WALLET_REQUEST); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_UNKNOWN_ERROR); + + wallet_client_->SendAutocheckoutStatus(autofill::SUCCESS, + GURL(kMerchantUrl), + "google_transaction_id"); + VerifyAndFinishRequest(net::HTTP_INTERNAL_SERVER_ERROR, + kSendAutocheckoutStatusOfSuccessValidRequest, + kErrorTypeMissingInResponse); +} + +TEST_F(WalletClientTest, NetworkFailureOnExpectedVoidResponse) { + EXPECT_CALL(delegate_, OnNetworkError(net::HTTP_UNAUTHORIZED)).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SEND_STATUS, 1); + delegate_.ExpectBaselineMetrics(NO_ESCROW_REQUEST, HAS_WALLET_REQUEST); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_NETWORK_ERROR); + + wallet_client_->SendAutocheckoutStatus(autofill::SUCCESS, + GURL(kMerchantUrl), + "google_transaction_id"); + VerifyAndFinishRequest(net::HTTP_UNAUTHORIZED, + kSendAutocheckoutStatusOfSuccessValidRequest, + std::string()); +} + +TEST_F(WalletClientTest, NetworkFailureOnExpectedResponse) { + EXPECT_CALL(delegate_, OnNetworkError(net::HTTP_UNAUTHORIZED)).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::GET_WALLET_ITEMS, + 1); + delegate_.ExpectBaselineMetrics(NO_ESCROW_REQUEST, HAS_WALLET_REQUEST); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_NETWORK_ERROR); + + wallet_client_->GetWalletItems(GURL(kMerchantUrl)); + VerifyAndFinishRequest(net::HTTP_UNAUTHORIZED, + kGetWalletItemsValidRequest, + std::string()); +} + +TEST_F(WalletClientTest, RequestError) { + EXPECT_CALL(delegate_, OnWalletError(WalletClient::BAD_REQUEST)).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SEND_STATUS, 1); + delegate_.ExpectBaselineMetrics(NO_ESCROW_REQUEST, HAS_WALLET_REQUEST); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_BAD_REQUEST); + + wallet_client_->SendAutocheckoutStatus(autofill::SUCCESS, + GURL(kMerchantUrl), + "google_transaction_id"); + VerifyAndFinishRequest(net::HTTP_BAD_REQUEST, + kSendAutocheckoutStatusOfSuccessValidRequest, + std::string()); +} + +TEST_F(WalletClientTest, GetFullWalletSuccess) { + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::GET_FULL_WALLET, 1); + delegate_.ExpectBaselineMetrics(HAS_ESCROW_REQUEST, HAS_WALLET_REQUEST); + + WalletClient::FullWalletRequest full_wallet_request( + "instrument_id", + "shipping_address_id", + GURL(kMerchantUrl), + "google_transaction_id", + std::vector<WalletClient::RiskCapability>()); + wallet_client_->GetFullWallet(full_wallet_request); + + DoEncryptionOrEscrowRequest(net::HTTP_OK, + "session_material|encrypted_one_time_pad"); + + VerifyAndFinishRequest(net::HTTP_OK, + kGetFullWalletValidRequest, + kGetFullWalletValidResponse); + EXPECT_EQ(1U, delegate_.full_wallets_received()); +} + +TEST_F(WalletClientTest, GetFullWalletWithRiskCapabilitesSuccess) { + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::GET_FULL_WALLET, 1); + delegate_.ExpectBaselineMetrics(HAS_ESCROW_REQUEST, HAS_WALLET_REQUEST); + + std::vector<WalletClient::RiskCapability> risk_capabilities; + risk_capabilities.push_back(WalletClient::VERIFY_CVC); + WalletClient::FullWalletRequest full_wallet_request( + "instrument_id", + "shipping_address_id", + GURL(kMerchantUrl), + "google_transaction_id", + risk_capabilities); + wallet_client_->GetFullWallet(full_wallet_request); + + DoEncryptionOrEscrowRequest(net::HTTP_OK, + "session_material|encrypted_one_time_pad"); + + VerifyAndFinishRequest(net::HTTP_OK, + kGetFullWalletWithRiskCapabilitesValidRequest, + kGetFullWalletValidResponse); + EXPECT_EQ(1U, delegate_.full_wallets_received()); +} + +TEST_F(WalletClientTest, GetFullWalletEncryptionDown) { + EXPECT_CALL(delegate_, + OnNetworkError(net::HTTP_INTERNAL_SERVER_ERROR)).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::GET_FULL_WALLET, 0); + delegate_.ExpectBaselineMetrics(HAS_ESCROW_REQUEST, NO_WALLET_REQUEST); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_NETWORK_ERROR); + + WalletClient::FullWalletRequest full_wallet_request( + "instrument_id", + "shipping_address_id", + GURL(kMerchantUrl), + "google_transaction_id", + std::vector<WalletClient::RiskCapability>()); + wallet_client_->GetFullWallet(full_wallet_request); + + DoEncryptionOrEscrowRequest(net::HTTP_INTERNAL_SERVER_ERROR, std::string()); + + EXPECT_EQ(0U, delegate_.full_wallets_received()); +} + +TEST_F(WalletClientTest, GetFullWalletEncryptionMalformed) { + EXPECT_CALL(delegate_, OnMalformedResponse()).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::GET_FULL_WALLET, 0); + delegate_.ExpectBaselineMetrics(HAS_ESCROW_REQUEST, NO_WALLET_REQUEST); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + + WalletClient::FullWalletRequest full_wallet_request( + "instrument_id", + "shipping_address_id", + GURL(kMerchantUrl), + "google_transaction_id", + std::vector<WalletClient::RiskCapability>()); + wallet_client_->GetFullWallet(full_wallet_request); + + DoEncryptionOrEscrowRequest(net::HTTP_OK, + "session_material:encrypted_one_time_pad"); + + EXPECT_EQ(0U, delegate_.full_wallets_received()); +} + +TEST_F(WalletClientTest, GetFullWalletMalformedResponse) { + EXPECT_CALL(delegate_, OnMalformedResponse()).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::GET_FULL_WALLET, 1); + delegate_.ExpectBaselineMetrics(HAS_ESCROW_REQUEST, HAS_WALLET_REQUEST); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + + WalletClient::FullWalletRequest full_wallet_request( + "instrument_id", + "shipping_address_id", + GURL(kMerchantUrl), + "google_transaction_id", + std::vector<WalletClient::RiskCapability>()); + wallet_client_->GetFullWallet(full_wallet_request); + + DoEncryptionOrEscrowRequest(net::HTTP_OK, + "session_material|encrypted_one_time_pad"); + + VerifyAndFinishRequest(net::HTTP_OK, + kGetFullWalletValidRequest, + kGetFullWalletInvalidResponse); + EXPECT_EQ(0U, delegate_.full_wallets_received()); +} + +TEST_F(WalletClientTest, AcceptLegalDocuments) { + EXPECT_CALL(delegate_, OnDidAcceptLegalDocuments()).Times(1); + delegate_.ExpectLogWalletApiCallDuration( + AutofillMetrics::ACCEPT_LEGAL_DOCUMENTS, + 1); + delegate_.ExpectBaselineMetrics(NO_ESCROW_REQUEST, HAS_WALLET_REQUEST); + + ScopedVector<WalletItems::LegalDocument> docs; + base::DictionaryValue document; + document.SetString("legal_document_id", "doc_id_1"); + document.SetString("display_name", "doc_1"); + docs.push_back( + WalletItems::LegalDocument::CreateLegalDocument(document).release()); + document.SetString("legal_document_id", "doc_id_2"); + document.SetString("display_name", "doc_2"); + docs.push_back( + WalletItems::LegalDocument::CreateLegalDocument(document).release()); + docs.push_back( + WalletItems::LegalDocument::CreatePrivacyPolicyDocument().release()); + wallet_client_->AcceptLegalDocuments(docs.get(), + kGoogleTransactionId, + GURL(kMerchantUrl)); + VerifyAndFinishRequest(net::HTTP_OK, + kAcceptLegalDocumentsValidRequest, + ")}'"); // Invalid JSON. Should be ignored. +} + +TEST_F(WalletClientTest, AuthenticateInstrumentSucceeded) { + EXPECT_CALL(delegate_, OnDidAuthenticateInstrument(true)).Times(1); + delegate_.ExpectLogWalletApiCallDuration( + AutofillMetrics::AUTHENTICATE_INSTRUMENT, + 1); + delegate_.ExpectBaselineMetrics(HAS_ESCROW_REQUEST, HAS_WALLET_REQUEST); + + wallet_client_->AuthenticateInstrument("instrument_id", + "cvv", + "obfuscated_gaia_id"); + + DoEncryptionOrEscrowRequest(net::HTTP_OK, "escrow_handle"); + + VerifyAndFinishRequest(net::HTTP_OK, + kAuthenticateInstrumentValidRequest, + kAuthenticateInstrumentSuccessResponse); +} + +TEST_F(WalletClientTest, AuthenticateInstrumentFailed) { + EXPECT_CALL(delegate_, OnDidAuthenticateInstrument(false)).Times(1); + delegate_.ExpectLogWalletApiCallDuration( + AutofillMetrics::AUTHENTICATE_INSTRUMENT, + 1); + delegate_.ExpectBaselineMetrics(HAS_ESCROW_REQUEST, HAS_WALLET_REQUEST); + + wallet_client_->AuthenticateInstrument("instrument_id", + "cvv", + "obfuscated_gaia_id"); + + DoEncryptionOrEscrowRequest(net::HTTP_OK, "escrow_handle"); + + VerifyAndFinishRequest(net::HTTP_OK, + kAuthenticateInstrumentValidRequest, + kAuthenticateInstrumentFailureResponse); +} + +TEST_F(WalletClientTest, AuthenticateInstrumentEscrowDown) { + EXPECT_CALL(delegate_, + OnNetworkError(net::HTTP_INTERNAL_SERVER_ERROR)).Times(1); + delegate_.ExpectLogWalletApiCallDuration( + AutofillMetrics::AUTHENTICATE_INSTRUMENT, + 0); + delegate_.ExpectBaselineMetrics(HAS_ESCROW_REQUEST, NO_WALLET_REQUEST); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_NETWORK_ERROR); + + wallet_client_->AuthenticateInstrument("instrument_id", + "cvv", + "obfuscated_gaia_id"); + + DoEncryptionOrEscrowRequest(net::HTTP_INTERNAL_SERVER_ERROR, std::string()); +} + +TEST_F(WalletClientTest, AuthenticateInstrumentEscrowMalformed) { + EXPECT_CALL(delegate_, OnMalformedResponse()).Times(1); + delegate_.ExpectLogWalletApiCallDuration( + AutofillMetrics::AUTHENTICATE_INSTRUMENT, + 0); + delegate_.ExpectBaselineMetrics(HAS_ESCROW_REQUEST, NO_WALLET_REQUEST); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + + wallet_client_->AuthenticateInstrument("instrument_id", + "cvv", + "obfuscated_gaia_id"); + + DoEncryptionOrEscrowRequest(net::HTTP_OK, std::string()); +} + +TEST_F(WalletClientTest, AuthenticateInstrumentFailedMalformedResponse) { + EXPECT_CALL(delegate_, OnMalformedResponse()).Times(1); + delegate_.ExpectLogWalletApiCallDuration( + AutofillMetrics::AUTHENTICATE_INSTRUMENT, + 1); + delegate_.ExpectBaselineMetrics(HAS_ESCROW_REQUEST, HAS_WALLET_REQUEST); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + + wallet_client_->AuthenticateInstrument("instrument_id", + "cvv", + "obfuscated_gaia_id"); + + DoEncryptionOrEscrowRequest(net::HTTP_OK, "escrow_handle"); + + VerifyAndFinishRequest(net::HTTP_OK, + kAuthenticateInstrumentValidRequest, + kSaveInvalidResponse); +} + +// TODO(ahutter): Add failure tests for GetWalletItems. + +TEST_F(WalletClientTest, GetWalletItems) { + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::GET_WALLET_ITEMS, + 1); + delegate_.ExpectBaselineMetrics(NO_ESCROW_REQUEST, HAS_WALLET_REQUEST); + + wallet_client_->GetWalletItems(GURL(kMerchantUrl)); + + VerifyAndFinishRequest(net::HTTP_OK, + kGetWalletItemsValidRequest, + kGetWalletItemsValidResponse); + EXPECT_EQ(1U, delegate_.wallet_items_received()); +} + +TEST_F(WalletClientTest, SaveAddressSucceeded) { + EXPECT_CALL(delegate_, + OnDidSaveAddress("saved_address_id", + std::vector<RequiredAction>())).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_ADDRESS, 1); + delegate_.ExpectBaselineMetrics(NO_ESCROW_REQUEST, HAS_WALLET_REQUEST); + + scoped_ptr<Address> address = GetTestSaveableAddress(); + wallet_client_->SaveAddress(*address, GURL(kMerchantUrl)); + VerifyAndFinishRequest(net::HTTP_OK, + kSaveAddressValidRequest, + kSaveAddressValidResponse); +} + +TEST_F(WalletClientTest, SaveAddressWithRequiredActionsSucceeded) { + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_ADDRESS, 1); + delegate_.ExpectBaselineMetrics(NO_ESCROW_REQUEST, HAS_WALLET_REQUEST); + delegate_.ExpectWalletRequiredActionMetric( + AutofillMetrics::REQUIRE_PHONE_NUMBER); + delegate_.ExpectWalletRequiredActionMetric( + AutofillMetrics::INVALID_FORM_FIELD); + + std::vector<RequiredAction> required_actions; + required_actions.push_back(REQUIRE_PHONE_NUMBER); + required_actions.push_back(INVALID_FORM_FIELD); + + EXPECT_CALL(delegate_, + OnDidSaveAddress(std::string(), + required_actions)).Times(1); + + scoped_ptr<Address> address = GetTestSaveableAddress(); + wallet_client_->SaveAddress(*address, GURL(kMerchantUrl)); + VerifyAndFinishRequest(net::HTTP_OK, + kSaveAddressValidRequest, + kSaveAddressWithRequiredActionsValidResponse); +} + +TEST_F(WalletClientTest, SaveAddressFailedInvalidRequiredAction) { + EXPECT_CALL(delegate_, OnMalformedResponse()).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_ADDRESS, 1); + delegate_.ExpectBaselineMetrics(NO_ESCROW_REQUEST, HAS_WALLET_REQUEST); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + + scoped_ptr<Address> address = GetTestSaveableAddress(); + wallet_client_->SaveAddress(*address, GURL(kMerchantUrl)); + VerifyAndFinishRequest(net::HTTP_OK, + kSaveAddressValidRequest, + kSaveWithInvalidRequiredActionsResponse); +} + +TEST_F(WalletClientTest, SaveAddressFailedMalformedResponse) { + EXPECT_CALL(delegate_, OnMalformedResponse()).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_ADDRESS, 1); + delegate_.ExpectBaselineMetrics(NO_ESCROW_REQUEST, HAS_WALLET_REQUEST); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + + scoped_ptr<Address> address = GetTestSaveableAddress(); + wallet_client_->SaveAddress(*address, GURL(kMerchantUrl)); + VerifyAndFinishRequest(net::HTTP_OK, + kSaveAddressValidRequest, + kSaveInvalidResponse); +} + +TEST_F(WalletClientTest, SaveInstrumentSucceeded) { + EXPECT_CALL(delegate_, + OnDidSaveInstrument("instrument_id", + std::vector<RequiredAction>())).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_INSTRUMENT, 1); + delegate_.ExpectBaselineMetrics(HAS_ESCROW_REQUEST, HAS_WALLET_REQUEST); + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + wallet_client_->SaveInstrument(*instrument, + "obfuscated_gaia_id", + GURL(kMerchantUrl)); + + DoEncryptionOrEscrowRequest(net::HTTP_OK, "escrow_handle"); + + VerifyAndFinishRequest(net::HTTP_OK, + kSaveInstrumentValidRequest, + kSaveInstrumentValidResponse); +} + +TEST_F(WalletClientTest, SaveInstrumentWithRequiredActionsSucceeded) { + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_INSTRUMENT, 1); + delegate_.ExpectBaselineMetrics(HAS_ESCROW_REQUEST, HAS_WALLET_REQUEST); + delegate_.ExpectWalletRequiredActionMetric( + AutofillMetrics::REQUIRE_PHONE_NUMBER); + delegate_.ExpectWalletRequiredActionMetric( + AutofillMetrics::INVALID_FORM_FIELD); + + std::vector<RequiredAction> required_actions; + required_actions.push_back(REQUIRE_PHONE_NUMBER); + required_actions.push_back(INVALID_FORM_FIELD); + + EXPECT_CALL(delegate_, + OnDidSaveInstrument(std::string(), + required_actions)).Times(1); + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + wallet_client_->SaveInstrument(*instrument, + "obfuscated_gaia_id", + GURL(kMerchantUrl)); + + DoEncryptionOrEscrowRequest(net::HTTP_OK, "escrow_handle"); + + VerifyAndFinishRequest(net::HTTP_OK, + kSaveInstrumentValidRequest, + kSaveInstrumentWithRequiredActionsValidResponse); +} + +TEST_F(WalletClientTest, SaveInstrumentFailedInvalidRequiredActions) { + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_INSTRUMENT, 1); + delegate_.ExpectBaselineMetrics(HAS_ESCROW_REQUEST, HAS_WALLET_REQUEST); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + + EXPECT_CALL(delegate_, OnMalformedResponse()); + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + wallet_client_->SaveInstrument(*instrument, + "obfuscated_gaia_id", + GURL(kMerchantUrl)); + + DoEncryptionOrEscrowRequest(net::HTTP_OK, "escrow_handle"); + + VerifyAndFinishRequest(net::HTTP_OK, + kSaveInstrumentValidRequest, + kSaveWithInvalidRequiredActionsResponse); +} + +TEST_F(WalletClientTest, SaveInstrumentEscrowDown) { + EXPECT_CALL(delegate_, + OnNetworkError(net::HTTP_INTERNAL_SERVER_ERROR)).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_INSTRUMENT, 0); + delegate_.ExpectBaselineMetrics(HAS_ESCROW_REQUEST, NO_WALLET_REQUEST); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_NETWORK_ERROR); + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + wallet_client_->SaveInstrument(*instrument, + "obfuscated_gaia_id", + GURL(kMerchantUrl)); + + DoEncryptionOrEscrowRequest(net::HTTP_INTERNAL_SERVER_ERROR, std::string()); +} + +TEST_F(WalletClientTest, SaveInstrumentEscrowMalformed) { + EXPECT_CALL(delegate_, OnMalformedResponse()).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_INSTRUMENT, 0); + delegate_.ExpectBaselineMetrics(HAS_ESCROW_REQUEST, NO_WALLET_REQUEST); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + wallet_client_->SaveInstrument(*instrument, + "obfuscated_gaia_id", + GURL(kMerchantUrl)); + + DoEncryptionOrEscrowRequest(net::HTTP_OK, std::string()); +} + +TEST_F(WalletClientTest, SaveInstrumentFailedMalformedResponse) { + EXPECT_CALL(delegate_, OnMalformedResponse()).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_INSTRUMENT, 1); + delegate_.ExpectBaselineMetrics(HAS_ESCROW_REQUEST, HAS_WALLET_REQUEST); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + wallet_client_->SaveInstrument(*instrument, + "obfuscated_gaia_id", + GURL(kMerchantUrl)); + + DoEncryptionOrEscrowRequest(net::HTTP_OK, "escrow_handle"); + + VerifyAndFinishRequest(net::HTTP_OK, + kSaveInstrumentValidRequest, + kSaveInvalidResponse); +} + +TEST_F(WalletClientTest, SaveInstrumentAndAddressSucceeded) { + EXPECT_CALL(delegate_, + OnDidSaveInstrumentAndAddress( + "saved_instrument_id", + "saved_address_id", + std::vector<RequiredAction>())).Times(1); + delegate_.ExpectLogWalletApiCallDuration( + AutofillMetrics::SAVE_INSTRUMENT_AND_ADDRESS, + 1); + delegate_.ExpectBaselineMetrics(HAS_ESCROW_REQUEST, HAS_WALLET_REQUEST); + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + scoped_ptr<Address> address = GetTestSaveableAddress(); + wallet_client_->SaveInstrumentAndAddress(*instrument, + *address, + "obfuscated_gaia_id", + GURL(kMerchantUrl)); + + DoEncryptionOrEscrowRequest(net::HTTP_OK, "escrow_handle"); + VerifyAndFinishRequest(net::HTTP_OK, + kSaveInstrumentAndAddressValidRequest, + kSaveInstrumentAndAddressValidResponse); +} + +TEST_F(WalletClientTest, SaveInstrumentAndAddressWithRequiredActionsSucceeded) { + delegate_.ExpectLogWalletApiCallDuration( + AutofillMetrics::SAVE_INSTRUMENT_AND_ADDRESS, + 1); + delegate_.ExpectBaselineMetrics(HAS_ESCROW_REQUEST, HAS_WALLET_REQUEST); + delegate_.ExpectWalletRequiredActionMetric( + AutofillMetrics::REQUIRE_PHONE_NUMBER); + delegate_.ExpectWalletRequiredActionMetric( + AutofillMetrics::INVALID_FORM_FIELD); + + std::vector<RequiredAction> required_actions; + required_actions.push_back(REQUIRE_PHONE_NUMBER); + required_actions.push_back(INVALID_FORM_FIELD); + + EXPECT_CALL(delegate_, + OnDidSaveInstrumentAndAddress( + std::string(), + std::string(), + required_actions)).Times(1); + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + scoped_ptr<Address> address = GetTestSaveableAddress(); + wallet_client_->SaveInstrumentAndAddress(*instrument, + *address, + "obfuscated_gaia_id", + GURL(kMerchantUrl)); + + DoEncryptionOrEscrowRequest(net::HTTP_OK, "escrow_handle"); + VerifyAndFinishRequest( + net::HTTP_OK, + kSaveInstrumentAndAddressValidRequest, + kSaveInstrumentAndAddressWithRequiredActionsValidResponse); +} + +TEST_F(WalletClientTest, SaveInstrumentAndAddressFailedInvalidRequiredAction) { + EXPECT_CALL(delegate_, OnMalformedResponse()).Times(1); + delegate_.ExpectLogWalletApiCallDuration( + AutofillMetrics::SAVE_INSTRUMENT_AND_ADDRESS, + 1); + delegate_.ExpectBaselineMetrics(HAS_ESCROW_REQUEST, HAS_WALLET_REQUEST); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + scoped_ptr<Address> address = GetTestSaveableAddress(); + wallet_client_->SaveInstrumentAndAddress(*instrument, + *address, + "obfuscated_gaia_id", + GURL(kMerchantUrl)); + + DoEncryptionOrEscrowRequest(net::HTTP_OK, "escrow_handle"); + + VerifyAndFinishRequest(net::HTTP_OK, + kSaveInstrumentAndAddressValidRequest, + kSaveWithInvalidRequiredActionsResponse); +} + +TEST_F(WalletClientTest, SaveInstrumentAndAddressEscrowDown) { + EXPECT_CALL(delegate_, + OnNetworkError(net::HTTP_INTERNAL_SERVER_ERROR)).Times(1); + delegate_.ExpectLogWalletApiCallDuration( + AutofillMetrics::SAVE_INSTRUMENT_AND_ADDRESS, + 0); + delegate_.ExpectBaselineMetrics(HAS_ESCROW_REQUEST, NO_WALLET_REQUEST); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_NETWORK_ERROR); + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + scoped_ptr<Address> address = GetTestSaveableAddress(); + wallet_client_->SaveInstrumentAndAddress(*instrument, + *address, + "obfuscated_gaia_id", + GURL(kMerchantUrl)); + + DoEncryptionOrEscrowRequest(net::HTTP_INTERNAL_SERVER_ERROR, std::string()); +} + +TEST_F(WalletClientTest, SaveInstrumentAndAddressEscrowMalformed) { + EXPECT_CALL(delegate_, OnMalformedResponse()).Times(1); + delegate_.ExpectLogWalletApiCallDuration( + AutofillMetrics::SAVE_INSTRUMENT_AND_ADDRESS, + 0); + delegate_.ExpectBaselineMetrics(HAS_ESCROW_REQUEST, NO_WALLET_REQUEST); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + scoped_ptr<Address> address = GetTestSaveableAddress(); + wallet_client_->SaveInstrumentAndAddress(*instrument, + *address, + "obfuscated_gaia_id", + GURL(kMerchantUrl)); + + DoEncryptionOrEscrowRequest(net::HTTP_OK, std::string()); +} + +TEST_F(WalletClientTest, SaveInstrumentAndAddressFailedAddressMissing) { + EXPECT_CALL(delegate_, OnMalformedResponse()).Times(1); + delegate_.ExpectLogWalletApiCallDuration( + AutofillMetrics::SAVE_INSTRUMENT_AND_ADDRESS, + 1); + delegate_.ExpectBaselineMetrics(HAS_ESCROW_REQUEST, HAS_WALLET_REQUEST); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + scoped_ptr<Address> address = GetTestSaveableAddress(); + wallet_client_->SaveInstrumentAndAddress(*instrument, + *address, + "obfuscated_gaia_id", + GURL(kMerchantUrl)); + + DoEncryptionOrEscrowRequest(net::HTTP_OK, "escrow_handle"); + + VerifyAndFinishRequest(net::HTTP_OK, + kSaveInstrumentAndAddressValidRequest, + kSaveInstrumentAndAddressMissingAddressResponse); +} + +TEST_F(WalletClientTest, SaveInstrumentAndAddressFailedInstrumentMissing) { + EXPECT_CALL(delegate_, OnMalformedResponse()).Times(1); + delegate_.ExpectLogWalletApiCallDuration( + AutofillMetrics::SAVE_INSTRUMENT_AND_ADDRESS, + 1); + delegate_.ExpectBaselineMetrics(HAS_ESCROW_REQUEST, HAS_WALLET_REQUEST); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + scoped_ptr<Address> address = GetTestSaveableAddress(); + wallet_client_->SaveInstrumentAndAddress(*instrument, + *address, + "obfuscated_gaia_id", + GURL(kMerchantUrl)); + + DoEncryptionOrEscrowRequest(net::HTTP_OK, "escrow_handle"); + + VerifyAndFinishRequest(net::HTTP_OK, + kSaveInstrumentAndAddressValidRequest, + kSaveInstrumentAndAddressMissingInstrumentResponse); +} + +TEST_F(WalletClientTest, UpdateAddressSucceeded) { + EXPECT_CALL(delegate_, + OnDidUpdateAddress("shipping_address_id", + std::vector<RequiredAction>())).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::UPDATE_ADDRESS, 1); + delegate_.ExpectBaselineMetrics(NO_ESCROW_REQUEST, HAS_WALLET_REQUEST); + + scoped_ptr<Address> address = GetTestShippingAddress(); + address->set_object_id("shipping_address_id"); + + wallet_client_->UpdateAddress(*address, GURL(kMerchantUrl)); + VerifyAndFinishRequest(net::HTTP_OK, + kUpdateAddressValidRequest, + kUpdateAddressValidResponse); +} + +TEST_F(WalletClientTest, UpdateAddressWithRequiredActionsSucceeded) { + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::UPDATE_ADDRESS, 1); + delegate_.ExpectBaselineMetrics(NO_ESCROW_REQUEST, HAS_WALLET_REQUEST); + delegate_.ExpectWalletRequiredActionMetric( + AutofillMetrics::REQUIRE_PHONE_NUMBER); + delegate_.ExpectWalletRequiredActionMetric( + AutofillMetrics::INVALID_FORM_FIELD); + + std::vector<RequiredAction> required_actions; + required_actions.push_back(REQUIRE_PHONE_NUMBER); + required_actions.push_back(INVALID_FORM_FIELD); + + EXPECT_CALL(delegate_, + OnDidUpdateAddress(std::string(), required_actions)).Times(1); + + scoped_ptr<Address> address = GetTestShippingAddress(); + address->set_object_id("shipping_address_id"); + + wallet_client_->UpdateAddress(*address, GURL(kMerchantUrl)); + VerifyAndFinishRequest(net::HTTP_OK, + kUpdateAddressValidRequest, + kUpdateWithRequiredActionsValidResponse); +} + +TEST_F(WalletClientTest, UpdateAddressFailedInvalidRequiredAction) { + EXPECT_CALL(delegate_, OnMalformedResponse()).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::UPDATE_ADDRESS, 1); + delegate_.ExpectBaselineMetrics(NO_ESCROW_REQUEST, HAS_WALLET_REQUEST); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + + scoped_ptr<Address> address = GetTestShippingAddress(); + address->set_object_id("shipping_address_id"); + + wallet_client_->UpdateAddress(*address, GURL(kMerchantUrl)); + VerifyAndFinishRequest(net::HTTP_OK, + kUpdateAddressValidRequest, + kSaveWithInvalidRequiredActionsResponse); +} + +TEST_F(WalletClientTest, UpdateAddressMalformedResponse) { + EXPECT_CALL(delegate_, OnMalformedResponse()).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::UPDATE_ADDRESS, 1); + delegate_.ExpectBaselineMetrics(NO_ESCROW_REQUEST, HAS_WALLET_REQUEST); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + + scoped_ptr<Address> address = GetTestShippingAddress(); + address->set_object_id("shipping_address_id"); + + wallet_client_->UpdateAddress(*address, GURL(kMerchantUrl)); + VerifyAndFinishRequest(net::HTTP_OK, + kUpdateAddressValidRequest, + kUpdateMalformedResponse); +} + +TEST_F(WalletClientTest, UpdateInstrumentAddressSucceeded) { + EXPECT_CALL(delegate_, + OnDidUpdateInstrument("instrument_id", + std::vector<RequiredAction>())).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::UPDATE_INSTRUMENT, + 1); + delegate_.ExpectBaselineMetrics(NO_ESCROW_REQUEST, HAS_WALLET_REQUEST); + + WalletClient::UpdateInstrumentRequest update_instrument_request( + "instrument_id", + GURL(kMerchantUrl)); + + wallet_client_->UpdateInstrument(update_instrument_request, GetTestAddress()); + + VerifyAndFinishRequest(net::HTTP_OK, + kUpdateInstrumentAddressValidRequest, + kUpdateInstrumentValidResponse); +} + +TEST_F(WalletClientTest, UpdateInstrumentExpirationDateSuceeded) { + EXPECT_CALL(delegate_, + OnDidUpdateInstrument("instrument_id", + std::vector<RequiredAction>())).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::UPDATE_INSTRUMENT, + 1); + delegate_.ExpectBaselineMetrics(HAS_ESCROW_REQUEST, HAS_WALLET_REQUEST); + + WalletClient::UpdateInstrumentRequest update_instrument_request( + "instrument_id", + GURL(kMerchantUrl)); + update_instrument_request.expiration_month = 12; + update_instrument_request.expiration_year = 2015; + update_instrument_request.card_verification_number = + "card_verification_number"; + update_instrument_request.obfuscated_gaia_id = "obfuscated_gaia_id"; + wallet_client_->UpdateInstrument(update_instrument_request, + scoped_ptr<Address>()); + + DoEncryptionOrEscrowRequest(net::HTTP_OK, "escrow_handle"); + + VerifyAndFinishRequest(net::HTTP_OK, + kUpdateInstrumentExpirationDateValidRequest, + kUpdateInstrumentValidResponse); +} + +TEST_F(WalletClientTest, UpdateInstrumentAddressWithNameChangeSucceeded) { + EXPECT_CALL(delegate_, + OnDidUpdateInstrument("instrument_id", + std::vector<RequiredAction>())).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::UPDATE_INSTRUMENT, + 1); + delegate_.ExpectBaselineMetrics(HAS_ESCROW_REQUEST, HAS_WALLET_REQUEST); + + WalletClient::UpdateInstrumentRequest update_instrument_request( + "instrument_id", + GURL(kMerchantUrl)); + update_instrument_request.card_verification_number = + "card_verification_number"; + update_instrument_request.obfuscated_gaia_id = "obfuscated_gaia_id"; + + wallet_client_->UpdateInstrument(update_instrument_request, GetTestAddress()); + + DoEncryptionOrEscrowRequest(net::HTTP_OK, "escrow_handle"); + + VerifyAndFinishRequest(net::HTTP_OK, + kUpdateInstrumentAddressWithNameChangeValidRequest, + kUpdateInstrumentValidResponse); +} + +TEST_F(WalletClientTest, UpdateInstrumentAddressAndExpirationDateSucceeded) { + EXPECT_CALL(delegate_, + OnDidUpdateInstrument("instrument_id", + std::vector<RequiredAction>())).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::UPDATE_INSTRUMENT, + 1); + delegate_.ExpectBaselineMetrics(HAS_ESCROW_REQUEST, HAS_WALLET_REQUEST); + + WalletClient::UpdateInstrumentRequest update_instrument_request( + "instrument_id", + GURL(kMerchantUrl)); + update_instrument_request.expiration_month = 12; + update_instrument_request.expiration_year = 2015; + update_instrument_request.card_verification_number = + "card_verification_number"; + update_instrument_request.obfuscated_gaia_id = "obfuscated_gaia_id"; + wallet_client_->UpdateInstrument(update_instrument_request, GetTestAddress()); + + DoEncryptionOrEscrowRequest(net::HTTP_OK, "escrow_handle"); + + VerifyAndFinishRequest(net::HTTP_OK, + kUpdateInstrumentAddressAndExpirationDateValidRequest, + kUpdateInstrumentValidResponse); +} + +TEST_F(WalletClientTest, UpdateInstrumentWithRequiredActionsSucceeded) { + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::UPDATE_INSTRUMENT, + 1); + delegate_.ExpectBaselineMetrics(NO_ESCROW_REQUEST, HAS_WALLET_REQUEST); + delegate_.ExpectWalletRequiredActionMetric( + AutofillMetrics::REQUIRE_PHONE_NUMBER); + delegate_.ExpectWalletRequiredActionMetric( + AutofillMetrics::INVALID_FORM_FIELD); + + std::vector<RequiredAction> required_actions; + required_actions.push_back(REQUIRE_PHONE_NUMBER); + required_actions.push_back(INVALID_FORM_FIELD); + + EXPECT_CALL(delegate_, + OnDidUpdateInstrument(std::string(), + required_actions)).Times(1); + + WalletClient::UpdateInstrumentRequest update_instrument_request( + "instrument_id", + GURL(kMerchantUrl)); + + wallet_client_->UpdateInstrument(update_instrument_request, GetTestAddress()); + + VerifyAndFinishRequest(net::HTTP_OK, + kUpdateInstrumentAddressValidRequest, + kUpdateWithRequiredActionsValidResponse); +} + +TEST_F(WalletClientTest, UpdateInstrumentFailedInvalidRequiredAction) { + EXPECT_CALL(delegate_, OnMalformedResponse()).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::UPDATE_INSTRUMENT, + 1); + delegate_.ExpectBaselineMetrics(NO_ESCROW_REQUEST, HAS_WALLET_REQUEST); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + + WalletClient::UpdateInstrumentRequest update_instrument_request( + "instrument_id", + GURL(kMerchantUrl)); + + wallet_client_->UpdateInstrument(update_instrument_request, GetTestAddress()); + + VerifyAndFinishRequest(net::HTTP_OK, + kUpdateInstrumentAddressValidRequest, + kSaveWithInvalidRequiredActionsResponse); +} + +TEST_F(WalletClientTest, UpdateInstrumentEscrowFailed) { + EXPECT_CALL(delegate_, + OnNetworkError(net::HTTP_INTERNAL_SERVER_ERROR)).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::UPDATE_INSTRUMENT, + 0); + delegate_.ExpectBaselineMetrics(HAS_ESCROW_REQUEST, NO_WALLET_REQUEST); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_NETWORK_ERROR); + + WalletClient::UpdateInstrumentRequest update_instrument_request( + "instrument_id", + GURL(kMerchantUrl)); + update_instrument_request.card_verification_number = + "card_verification_number"; + update_instrument_request.obfuscated_gaia_id = "obfuscated_gaia_id"; + + wallet_client_->UpdateInstrument(update_instrument_request, GetTestAddress()); + + DoEncryptionOrEscrowRequest(net::HTTP_INTERNAL_SERVER_ERROR, std::string()); +} + +TEST_F(WalletClientTest, UpdateInstrumentMalformedResponse) { + EXPECT_CALL(delegate_, OnMalformedResponse()).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::UPDATE_INSTRUMENT, + 1); + delegate_.ExpectBaselineMetrics(NO_ESCROW_REQUEST, HAS_WALLET_REQUEST); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + + WalletClient::UpdateInstrumentRequest update_instrument_request( + "instrument_id", + GURL(kMerchantUrl)); + + wallet_client_->UpdateInstrument(update_instrument_request, GetTestAddress()); + + VerifyAndFinishRequest(net::HTTP_OK, + kUpdateInstrumentAddressValidRequest, + kUpdateMalformedResponse); +} + +TEST_F(WalletClientTest, SendAutocheckoutOfStatusSuccess) { + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SEND_STATUS, 1); + delegate_.ExpectBaselineMetrics(NO_ESCROW_REQUEST, HAS_WALLET_REQUEST); + + wallet_client_->SendAutocheckoutStatus(autofill::SUCCESS, + GURL(kMerchantUrl), + "google_transaction_id"); + VerifyAndFinishRequest(net::HTTP_OK, + kSendAutocheckoutStatusOfSuccessValidRequest, + ")]}"); // Invalid JSON. Should be ignored. +} + +TEST_F(WalletClientTest, SendAutocheckoutStatusOfFailure) { + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SEND_STATUS, 1); + delegate_.ExpectBaselineMetrics(NO_ESCROW_REQUEST, HAS_WALLET_REQUEST); + + wallet_client_->SendAutocheckoutStatus(autofill::CANNOT_PROCEED, + GURL(kMerchantUrl), + "google_transaction_id"); + VerifyAndFinishRequest(net::HTTP_OK, + kSendAutocheckoutStatusOfFailureValidRequest, + ")]}"); // Invalid JSON. Should be ignored. +} + +TEST_F(WalletClientTest, HasRequestInProgress) { + EXPECT_FALSE(wallet_client_->HasRequestInProgress()); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::GET_WALLET_ITEMS, + 1); + delegate_.ExpectBaselineMetrics(NO_ESCROW_REQUEST, HAS_WALLET_REQUEST); + + wallet_client_->GetWalletItems(GURL(kMerchantUrl)); + EXPECT_TRUE(wallet_client_->HasRequestInProgress()); + + VerifyAndFinishRequest(net::HTTP_OK, + kGetWalletItemsValidRequest, + kGetWalletItemsValidResponse); + EXPECT_FALSE(wallet_client_->HasRequestInProgress()); +} + +TEST_F(WalletClientTest, PendingRequest) { + EXPECT_EQ(0U, wallet_client_->pending_requests_.size()); + + // Shouldn't queue the first request. + delegate_.ExpectBaselineMetrics(NO_ESCROW_REQUEST, HAS_WALLET_REQUEST); + wallet_client_->GetWalletItems(GURL(kMerchantUrl)); + EXPECT_EQ(0U, wallet_client_->pending_requests_.size()); + testing::Mock::VerifyAndClear(delegate_.metric_logger()); + + wallet_client_->GetWalletItems(GURL(kMerchantUrl)); + EXPECT_EQ(1U, wallet_client_->pending_requests_.size()); + + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::GET_WALLET_ITEMS, + 1); + delegate_.ExpectBaselineMetrics(NO_ESCROW_REQUEST, HAS_WALLET_REQUEST); + VerifyAndFinishRequest(net::HTTP_OK, + kGetWalletItemsValidRequest, + kGetWalletItemsValidResponse); + EXPECT_EQ(0U, wallet_client_->pending_requests_.size()); + testing::Mock::VerifyAndClear(delegate_.metric_logger()); + + EXPECT_CALL(delegate_, OnWalletError( + WalletClient::SERVICE_UNAVAILABLE)).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::GET_WALLET_ITEMS, + 1); + delegate_.ExpectWalletErrorMetric( + AutofillMetrics::WALLET_SERVICE_UNAVAILABLE); + + // Finish the second request. + VerifyAndFinishRequest(net::HTTP_INTERNAL_SERVER_ERROR, + kGetWalletItemsValidRequest, + kErrorResponse); +} + +TEST_F(WalletClientTest, CancelRequests) { + ASSERT_EQ(0U, wallet_client_->pending_requests_.size()); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::GET_WALLET_ITEMS, + 0); + delegate_.ExpectBaselineMetrics(NO_ESCROW_REQUEST, HAS_WALLET_REQUEST); + + wallet_client_->GetWalletItems(GURL(kMerchantUrl)); + wallet_client_->GetWalletItems(GURL(kMerchantUrl)); + wallet_client_->GetWalletItems(GURL(kMerchantUrl)); + EXPECT_EQ(2U, wallet_client_->pending_requests_.size()); + + wallet_client_->CancelRequests(); + EXPECT_EQ(0U, wallet_client_->pending_requests_.size()); + EXPECT_FALSE(wallet_client_->HasRequestInProgress()); +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/content/browser/wallet/wallet_items.cc b/components/autofill/content/browser/wallet/wallet_items.cc new file mode 100644 index 0000000..3f4ff77 --- /dev/null +++ b/components/autofill/content/browser/wallet/wallet_items.cc @@ -0,0 +1,530 @@ +// Copyright 2013 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 "components/autofill/content/browser/wallet/wallet_items.h" + +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "components/autofill/browser/autofill_type.h" +#include "components/autofill/browser/credit_card.h" +#include "googleurl/src/gurl.h" +#include "grit/component_resources.h" +#include "grit/webkit_resources.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/image/image.h" + +namespace autofill { +namespace wallet { + +namespace { + +const char kLegalDocumentUrl[] = + "https://wallet.google.com/legaldocument?docId="; +const char kPrivacyNoticeUrl[] = "https://wallet.google.com/files/privacy.html"; + +// TODO(estade): move to base/. +template<class T> +bool VectorsAreEqual(const std::vector<T*>& a, const std::vector<T*>& b) { + if (a.size() != b.size()) + return false; + + for (size_t i = 0; i < a.size(); ++i) { + if (*a[i] != *b[i]) + return false; + } + + return true; +} + +WalletItems::MaskedInstrument::Type + TypeFromString(const std::string& type_string) { + if (type_string == "VISA") + return WalletItems::MaskedInstrument::VISA; + if (type_string == "MASTER_CARD") + return WalletItems::MaskedInstrument::MASTER_CARD; + if (type_string == "AMEX") + return WalletItems::MaskedInstrument::AMEX; + if (type_string == "DISCOVER") + return WalletItems::MaskedInstrument::DISCOVER; + if (type_string == "SOLO") + return WalletItems::MaskedInstrument::SOLO; + if (type_string == "MAESTRO") + return WalletItems::MaskedInstrument::MAESTRO; + if (type_string == "SWITCH") + return WalletItems::MaskedInstrument::SWITCH; + return WalletItems::MaskedInstrument::UNKNOWN; +} + +WalletItems::MaskedInstrument::Status + StatusFromString(const std::string& status_string) { + if (status_string == "AMEX_NOT_SUPPORTED") + return WalletItems::MaskedInstrument::AMEX_NOT_SUPPORTED; + if (status_string == "PENDING") + return WalletItems::MaskedInstrument::PENDING; + if (status_string == "VALID") + return WalletItems::MaskedInstrument::VALID; + if (status_string == "DECLINED") + return WalletItems::MaskedInstrument::DECLINED; + if (status_string == "DISABLED_FOR_THIS_MERCHANT") + return WalletItems::MaskedInstrument::DISABLED_FOR_THIS_MERCHANT; + if (status_string == "UNSUPPORTED_COUNTRY") + return WalletItems::MaskedInstrument::UNSUPPORTED_COUNTRY; + if (status_string == "EXPIRED") + return WalletItems::MaskedInstrument::EXPIRED; + if (status_string == "BILLING_INCOMPLETE") + return WalletItems::MaskedInstrument::BILLING_INCOMPLETE; + return WalletItems::MaskedInstrument::INAPPLICABLE; +} + +} // anonymous namespace + +WalletItems::MaskedInstrument::MaskedInstrument( + const base::string16& descriptive_name, + const WalletItems::MaskedInstrument::Type& type, + const std::vector<base::string16>& supported_currencies, + const base::string16& last_four_digits, + int expiration_month, + int expiration_year, + 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), + 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>(); + } + + base::string16 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<base::string16> 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) { + base::string16 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"; + + base::string16 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, + 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 (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); +} + +const WalletItems::MaskedInstrument* WalletItems::GetInstrumentById( + const std::string& object_id) const { + if (object_id.empty()) + return NULL; + + for (size_t i = 0; i < instruments_.size(); ++i) { + if (instruments_[i]->object_id() == object_id) + return instruments_[i]; + } + + return NULL; +} + +bool WalletItems::HasRequiredAction(RequiredAction action) const { + DCHECK(ActionAppliesToWalletItems(action)); + return std::find(required_actions_.begin(), + required_actions_.end(), + action) != required_actions_.end(); +} + +base::string16 WalletItems::MaskedInstrument::DisplayName() const { +#if defined(OS_ANDROID) + // TODO(aruslan): improve this stub implementation. + return descriptive_name(); +#else + return descriptive_name(); +#endif +} + +base::string16 WalletItems::MaskedInstrument::DisplayNameDetail() const { +#if defined(OS_ANDROID) + // TODO(aruslan): improve this stub implementation. + return address().DisplayName(); +#else + return base::string16(); +#endif +} + +base::string16 WalletItems::MaskedInstrument::TypeAndLastFourDigits() const { + base::string16 display_type; + + if (type_ == AMEX) + display_type = CreditCard::TypeForDisplay(kAmericanExpressCard); + else if (type_ == DISCOVER) + display_type = CreditCard::TypeForDisplay(kDiscoverCard); + else if (type_ == MASTER_CARD) + display_type = CreditCard::TypeForDisplay(kMasterCard); + else if (type_ == SOLO) + display_type = CreditCard::TypeForDisplay(kSoloCard); + else if (type_ == VISA) + display_type = CreditCard::TypeForDisplay(kVisaCard); + else + display_type = CreditCard::TypeForDisplay(kGenericCard); + + // TODO(dbeam): i18n. + return display_type + ASCIIToUTF16(" - ") + last_four_digits(); +} + +const gfx::Image& WalletItems::MaskedInstrument::CardIcon() const { + int idr = 0; + switch (type_) { + case AMEX: + idr = IDR_AUTOFILL_CC_AMEX; + break; + + case DISCOVER: + idr = IDR_AUTOFILL_CC_DISCOVER; + break; + + case MASTER_CARD: + idr = IDR_AUTOFILL_CC_MASTERCARD; + break; + + case SOLO: + idr = IDR_AUTOFILL_CC_SOLO; + break; + + case VISA: + idr = IDR_AUTOFILL_CC_VISA; + break; + + case MAESTRO: + case SWITCH: + case UNKNOWN: + idr = IDR_AUTOFILL_CC_GENERIC; + break; + } + + return ResourceBundle::GetSharedInstance().GetImageNamed(idr); +} + +base::string16 WalletItems::MaskedInstrument::GetInfo( + AutofillFieldType type, + const std::string& app_locale) const { + if (AutofillType(type).group() != AutofillType::CREDIT_CARD) + return address().GetInfo(type, app_locale); + + switch (type) { + case CREDIT_CARD_NAME: + return address().recipient_name(); + + case CREDIT_CARD_NUMBER: + return DisplayName(); + + case CREDIT_CARD_EXP_4_DIGIT_YEAR: + return base::IntToString16(expiration_year()); + + case CREDIT_CARD_VERIFICATION_CODE: + break; + + default: + NOTREACHED(); + } + + return base::string16(); +} + +WalletItems::LegalDocument::~LegalDocument() {} + +scoped_ptr<WalletItems::LegalDocument> + WalletItems::LegalDocument::CreateLegalDocument( + const base::DictionaryValue& dictionary) { + std::string id; + if (!dictionary.GetString("legal_document_id", &id)) { + DLOG(ERROR) << "Response from Google Wallet missing legal document id"; + return scoped_ptr<LegalDocument>(); + } + + base::string16 display_name; + if (!dictionary.GetString("display_name", &display_name)) { + DLOG(ERROR) << "Response from Google Wallet missing display name"; + return scoped_ptr<LegalDocument>(); + } + + return scoped_ptr<LegalDocument>(new LegalDocument(id, display_name)); +} + +scoped_ptr<WalletItems::LegalDocument> + WalletItems::LegalDocument::CreatePrivacyPolicyDocument() { + return scoped_ptr<LegalDocument>(new LegalDocument( + GURL(kPrivacyNoticeUrl), + l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_PRIVACY_POLICY_LINK))); +} + +bool WalletItems::LegalDocument::operator==(const LegalDocument& other) const { + return id_ == other.id_ && + url_ == other.url_ && + display_name_ == other.display_name_; +} + +bool WalletItems::LegalDocument::operator!=(const LegalDocument& other) const { + return !(*this == other); +} + +WalletItems::LegalDocument::LegalDocument(const std::string& id, + const base::string16& display_name) + : id_(id), + url_(kLegalDocumentUrl + id), + display_name_(display_name) {} + +WalletItems::LegalDocument::LegalDocument(const GURL& url, + const base::string16& display_name) + : url_(url), + display_name_(display_name) {} + +WalletItems::WalletItems(const std::vector<RequiredAction>& required_actions, + const std::string& google_transaction_id, + const std::string& default_instrument_id, + const std::string& default_address_id, + const std::string& obfuscated_gaia_id) + : required_actions_(required_actions), + google_transaction_id_(google_transaction_id), + default_instrument_id_(default_instrument_id), + default_address_id_(default_address_id), + obfuscated_gaia_id_(obfuscated_gaia_id) {} + +WalletItems::~WalletItems() {} + +scoped_ptr<WalletItems> + WalletItems::CreateWalletItems(const base::DictionaryValue& dictionary) { + std::vector<RequiredAction> 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_string; + if (required_action_list->GetString(i, &action_string)) { + RequiredAction action = ParseRequiredActionFromString(action_string); + if (!ActionAppliesToWalletItems(action)) { + DLOG(ERROR) << "Response from Google wallet with bad required action:" + " \"" << action_string << "\""; + return scoped_ptr<WalletItems>(); + } + required_action.push_back(action); + } + } + } else { + DVLOG(1) << "Response from Google wallet missing required actions"; + } + + std::string google_transaction_id; + if (!dictionary.GetString("google_transaction_id", &google_transaction_id) && + required_action.empty()) { + DLOG(ERROR) << "Response from Google wallet missing google transaction id"; + return scoped_ptr<WalletItems>(); + } + + 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"; + + std::string obfuscated_gaia_id; + if (!dictionary.GetString("obfuscated_gaia_id", &obfuscated_gaia_id)) + DVLOG(1) << "Response from Google wallet missing obfuscated gaia id"; + + scoped_ptr<WalletItems> wallet_items(new WalletItems(required_action, + google_transaction_id, + default_instrument_id, + default_address_id, + obfuscated_gaia_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>(); + } + } + } + + if (!legal_docs->empty()) { + // Always append the privacy policy link as well. + wallet_items->AddLegalDocument( + LegalDocument::CreatePrivacyPolicyDocument()); + } + } 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 { + return google_transaction_id_ == other.google_transaction_id_ && + default_instrument_id_ == other.default_instrument_id_ && + default_address_id_ == other.default_address_id_ && + required_actions_ == other.required_actions_ && + obfuscated_gaia_id_ == other.obfuscated_gaia_id_ && + VectorsAreEqual<MaskedInstrument>(instruments(), + other.instruments()) && + VectorsAreEqual<Address>(addresses(), other.addresses()) && + VectorsAreEqual<LegalDocument>(legal_documents(), + other.legal_documents()); +} + +bool WalletItems::operator!=(const WalletItems& other) const { + return !(*this == other); +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/content/browser/wallet/wallet_items.h b/components/autofill/content/browser/wallet/wallet_items.h new file mode 100644 index 0000000..08d0d1d --- /dev/null +++ b/components/autofill/content/browser/wallet/wallet_items.h @@ -0,0 +1,298 @@ +// Copyright 2013 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 COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_WALLET_ITEMS_H_ +#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_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 "base/string16.h" +#include "components/autofill/content/browser/wallet/required_action.h" +#include "components/autofill/content/browser/wallet/wallet_address.h" +#include "googleurl/src/gurl.h" + +namespace base { +class DictionaryValue; +} + +namespace gfx { +class Image; +} + +namespace autofill { + +FORWARD_DECLARE_TEST(WalletInstrumentWrapperTest, GetInfoCreditCardExpMonth); +FORWARD_DECLARE_TEST(WalletInstrumentWrapperTest, + GetDisplayTextEmptyWhenExpired); + +namespace wallet { + +class WalletItemsTest; + +// WalletItems is a collection of cards and addresses that a user picks from to +// construct a full wallet. 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 { + AMEX_NOT_SUPPORTED, + BILLING_INCOMPLETE, + DECLINED, + DISABLED_FOR_THIS_MERCHANT, // Deprecated. + 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; + + // Gets an image to display for this instrument. + const gfx::Image& CardIcon() const; + + // Returns a pair of strings that summarizes this CC, + // suitable for display to the user. + base::string16 DisplayName() const; + base::string16 DisplayNameDetail() const; + + // Gets info that corresponds with |type|. + base::string16 GetInfo(AutofillFieldType type, + const std::string& app_locale) const; + + // Returns the display type of the and last four digits (e.g. Visa - 4444). + base::string16 TypeAndLastFourDigits() const; + + const base::string16& descriptive_name() const { return descriptive_name_; } + const Type& type() const { return type_; } + const std::vector<base::string16>& supported_currencies() const { + return supported_currencies_; + } + const base::string16& last_four_digits() const { return last_four_digits_; } + int expiration_month() const { return expiration_month_; } + int expiration_year() const { return expiration_year_; } + 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 scoped_ptr<MaskedInstrument> GetTestMaskedInstrumentWithDetails( + const std::string&, scoped_ptr<Address> address, + Type type, Status status); + FRIEND_TEST_ALL_PREFIXES(::autofill::WalletInstrumentWrapperTest, + GetInfoCreditCardExpMonth); + FRIEND_TEST_ALL_PREFIXES(::autofill::WalletInstrumentWrapperTest, + GetDisplayTextEmptyWhenExpired); + FRIEND_TEST_ALL_PREFIXES(WalletItemsTest, CreateMaskedInstrument); + FRIEND_TEST_ALL_PREFIXES(WalletItemsTest, CreateWalletItems); + + MaskedInstrument(const base::string16& descriptve_name, + const Type& type, + const std::vector<base::string16>& supported_currencies, + const base::string16& last_four_digits, + int expiration_month, + int expiration_year, + scoped_ptr<Address> address, + const Status& status, + const std::string& object_id); + + // A user-provided description of the instrument. For example, "Google Visa + // Card". + base::string16 descriptive_name_; + + // The payment network of the instrument. For example, Visa. + Type type_; + + // |supported_currencies_| are ISO 4217 currency codes, e.g. USD. + std::vector<base::string16> supported_currencies_; + + // The last four digits of the primary account number of the instrument. + base::string16 last_four_digits_; + + // |expiration month_| should be 1-12. + int expiration_month_; + + // |expiration_year_| should be a 4-digit year. + int expiration_year_; + + // The billing address for the instrument. + scoped_ptr<Address> address_; + + // The current status of the instrument. For example, expired or declined. + Status status_; + + // Externalized Online Wallet id for this instrument. + 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. + static scoped_ptr<LegalDocument> + CreateLegalDocument(const base::DictionaryValue& dictionary); + + // Returns a document for the privacy policy (acceptance of which is not + // tracked by the server). + static scoped_ptr<LegalDocument> CreatePrivacyPolicyDocument(); + + bool operator==(const LegalDocument& other) const; + bool operator!=(const LegalDocument& other) const; + + const std::string& id() { return id_; } + const GURL& url() const { return url_; } + const base::string16& display_name() const { return display_name_; } + + private: + friend class WalletItemsTest; + FRIEND_TEST_ALL_PREFIXES(WalletItemsTest, CreateLegalDocument); + FRIEND_TEST_ALL_PREFIXES(WalletItemsTest, CreateWalletItems); + FRIEND_TEST_ALL_PREFIXES(WalletItemsTest, LegalDocumentUrl); + FRIEND_TEST_ALL_PREFIXES(WalletItemsTest, LegalDocumentEmptyId); + LegalDocument(const std::string& id, + const base::string16& display_name); + LegalDocument(const GURL& url, + const base::string16& display_name); + + // Externalized Online Wallet id for the document, or an empty string for + // documents not tracked by the server (such as the privacy policy). + std::string id_; + // The human-visitable URL that displays the document. + GURL url_; + // User displayable name for the document. + base::string16 display_name_; + 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()); + } + + // Return the corresponding instrument for |id| or NULL if it doesn't exist. + const WalletItems::MaskedInstrument* GetInstrumentById( + const std::string& object_id) const; + + // Whether or not |action| is in |required_actions_|. + bool HasRequiredAction(RequiredAction action) const; + + const std::vector<RequiredAction>& 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::string& obfuscated_gaia_id() const { return obfuscated_gaia_id_; } + const std::vector<LegalDocument*>& legal_documents() const { + return legal_documents_.get(); + } + + private: + friend class WalletItemsTest; + friend scoped_ptr<WalletItems> GetTestWalletItems(); + FRIEND_TEST_ALL_PREFIXES(WalletItemsTest, CreateWalletItems); + FRIEND_TEST_ALL_PREFIXES(WalletItemsTest, + CreateWalletItemsWithRequiredActions); + + WalletItems(const std::vector<RequiredAction>& required_actions, + const std::string& google_transaction_id, + const std::string& default_instrument_id, + const std::string& default_address_id, + const std::string& obfuscated_gaia_id); + + // Actions that must be completed by the user before a FullWallet can be + // issued to them by the Online Wallet service. + std::vector<RequiredAction> required_actions_; + + // The id for this transaction issued by Google. + std::string google_transaction_id_; + + // The id of the user's default instrument. + std::string default_instrument_id_; + + // The id of the user's default address. + std::string default_address_id_; + + // The externalized Gaia id of the user. + std::string obfuscated_gaia_id_; + + // The user's backing instruments. + ScopedVector<MaskedInstrument> instruments_; + + // The user's shipping addresses. + ScopedVector<Address> addresses_; + + // Legal documents the user must accept before using Online Wallet. + ScopedVector<LegalDocument> legal_documents_; + + DISALLOW_COPY_AND_ASSIGN(WalletItems); +}; + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_WALLET_ITEMS_H_ diff --git a/components/autofill/content/browser/wallet/wallet_items_unittest.cc b/components/autofill/content/browser/wallet/wallet_items_unittest.cc new file mode 100644 index 0000000..7bdf9d0 --- /dev/null +++ b/components/autofill/content/browser/wallet/wallet_items_unittest.cc @@ -0,0 +1,570 @@ +// Copyright 2013 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/strings/utf_string_conversions.h" +#include "base/values.h" +#include "components/autofill/content/browser/wallet/required_action.h" +#include "components/autofill/content/browser/wallet/wallet_items.h" +#include "googleurl/src/gurl.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," + " \"billing_address\":" + " {" + " \"name\":\"name\"," + " \"address1\":\"address1\"," + " \"address2\":\"address2\"," + " \"city\":\"city\"," + " \"state\":\"state\"," + " \"postal_code\":\"postal_code\"," + " \"phone_number\":\"phone_number\"," + " \"country_code\":\"country_code\"," + " \"type\":\"FULL\"" + " }," + " \"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," + " \"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," + " \"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," + " \"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," + " \"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," + " \"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," + " \"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\"" + "}"; + +const char kLegalDocumentMissingDocumentId[] = + "{" + " \"display_name\":\"display_name\"" + "}"; + +const char kLegalDocumentMissingDisplayName[] = + "{" + " \"legal_document_id\":\"doc_id\"" + "}"; + +const char kWalletItemsWithRequiredActions[] = + "{" + " \"obfuscated_gaia_id\":\"\"," + " \"required_action\":" + " [" + " \" setup_wallet\"," + " \" CHOOse_ANother_INSTRUMENT_OR_ADDRESS\"," + " \"AcCePt_ToS \"," + " \" \\tGAIA_auth \\n\\r\"," + " \"UPDATE_expiration_date\"," + " \"UPGRADE_min_ADDRESS \"," + " \" pAsSiVe_GAIA_auth \"," + " \" REQUIRE_PHONE_NUMBER\\t \"" + " ]" + "}"; + +const char kWalletItemsWithInvalidRequiredActions[] = + "{" + " \"obfuscated_gaia_id\":\"\"," + " \"required_action\":" + " [" + " \"verify_CVV\"," + " \"invalid_FORM_FIELD\"," + " \" 忍者の正体 \"" + " ]" + "}"; + +const char kWalletItemsMissingGoogleTransactionId[] = + "{" + " \"required_action\":" + " [" + " ]," + " \"instrument\":" + " [" + " {" + " \"descriptive_name\":\"descriptive_name\"," + " \"type\":\"VISA\"," + " \"supported_currency\":" + " [" + " \"currency\"" + " ]," + " \"last_four_digits\":\"last_four_digits\"," + " \"expiration_month\":12," + " \"expiration_year\":2012," + " \"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\":" + " [" + " \"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\"" + " }" + " }" + " ]," + " \"default_address_id\":\"default_address_id\"," + " \"obfuscated_gaia_id\":\"obfuscated_gaia_id\"," + " \"required_legal_document\":" + " [" + " {" + " \"legal_document_id\":\"doc_id\"," + " \"display_name\":\"display_name\"" + " }" + " ]" + "}"; + +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," + " \"billing_address\":" + " {" + " \"name\":\"name\"," + " \"address1\":\"address1\"," + " \"address2\":\"address2\"," + " \"city\":\"city\"," + " \"state\":\"state\"," + " \"postal_code\":\"postal_code\"," + " \"phone_number\":\"phone_number\"," + " \"country_code\":\"country_code\"," + " \"type\":\"FULL\"" + " }," + " \"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\":" + " [" + " \"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\"" + " }" + " }" + " ]," + " \"default_address_id\":\"default_address_id\"," + " \"obfuscated_gaia_id\":\"obfuscated_gaia_id\""; + +const char kRequiredLegalDocument[] = + " ," + " \"required_legal_document\":" + " [" + " {" + " \"legal_document_id\":\"doc_id\"," + " \"display_name\":\"display_name\"" + " }" + " ]"; + +const char kCloseJson[] = "}"; + +} // anonymous namespace + +namespace autofill { +namespace wallet { + +class WalletItemsTest : public testing::Test { + public: + WalletItemsTest() {} + protected: + void SetUpDictionary(const std::string& json) { + scoped_ptr<Value> value(base::JSONReader::Read(json)); + ASSERT_TRUE(value.get()); + ASSERT_TRUE(value->IsType(Value::TYPE_DICTIONARY)); + dict.reset(static_cast<DictionaryValue*>(value.release())); + } + scoped_ptr<DictionaryValue> dict; +}; + +TEST_F(WalletItemsTest, CreateMaskedInstrumentMissingStatus) { + SetUpDictionary(kMaskedInstrumentMissingStatus); + EXPECT_EQ(NULL, + WalletItems::MaskedInstrument::CreateMaskedInstrument(*dict).get()); +} + +TEST_F(WalletItemsTest, CreateMaskedInstrumentMissingType) { + SetUpDictionary(kMaskedInstrumentMissingType); + EXPECT_EQ(NULL, + WalletItems::MaskedInstrument::CreateMaskedInstrument(*dict).get()); +} + +TEST_F(WalletItemsTest, CreateMaskedInstrumentMissingLastFourDigits) { + SetUpDictionary(kMaskedInstrumentMissingLastFourDigits); + EXPECT_EQ(NULL, + WalletItems::MaskedInstrument::CreateMaskedInstrument(*dict).get()); +} + +TEST_F(WalletItemsTest, CreateMaskedInstrumentMissingAddress) { + SetUpDictionary(kMaskedInstrumentMissingAddress); + EXPECT_EQ(NULL, + WalletItems::MaskedInstrument::CreateMaskedInstrument(*dict).get()); +} + +TEST_F(WalletItemsTest, CreateMaskedInstrumentMalformedAddress) { + SetUpDictionary(kMaskedInstrumentMalformedAddress); + EXPECT_EQ(NULL, + WalletItems::MaskedInstrument::CreateMaskedInstrument(*dict).get()); +} + +TEST_F(WalletItemsTest, CreateMaskedInstrumentMissingObjectId) { + SetUpDictionary(kMaskedInstrumentMissingObjectId); + EXPECT_EQ(NULL, + WalletItems::MaskedInstrument::CreateMaskedInstrument(*dict).get()); +} + +TEST_F(WalletItemsTest, CreateMaskedInstrument) { + SetUpDictionary(kMaskedInstrument); + scoped_ptr<Address> address(new Address("country_code", + ASCIIToUTF16("name"), + ASCIIToUTF16("address1"), + ASCIIToUTF16("address2"), + ASCIIToUTF16("city"), + ASCIIToUTF16("state"), + ASCIIToUTF16("postal_code"), + ASCIIToUTF16("phone_number"), + std::string())); + std::vector<base::string16> supported_currencies; + supported_currencies.push_back(ASCIIToUTF16("currency")); + WalletItems::MaskedInstrument masked_instrument( + ASCIIToUTF16("descriptive_name"), + WalletItems::MaskedInstrument::VISA, + supported_currencies, + ASCIIToUTF16("last_four_digits"), + 12, + 2012, + address.Pass(), + WalletItems::MaskedInstrument::VALID, + "object_id"); + EXPECT_EQ(masked_instrument, + *WalletItems::MaskedInstrument::CreateMaskedInstrument(*dict)); +} + +TEST_F(WalletItemsTest, CreateLegalDocumentMissingDocId) { + SetUpDictionary(kLegalDocumentMissingDocumentId); + EXPECT_EQ(NULL, WalletItems::LegalDocument::CreateLegalDocument(*dict).get()); +} + +TEST_F(WalletItemsTest, CreateLegalDocumentMissingDisplayName) { + SetUpDictionary(kLegalDocumentMissingDisplayName); + EXPECT_EQ(NULL, WalletItems::LegalDocument::CreateLegalDocument(*dict).get()); +} + +TEST_F(WalletItemsTest, CreateLegalDocument) { + SetUpDictionary(kLegalDocument); + WalletItems::LegalDocument expected("doc_id", ASCIIToUTF16("display_name")); + EXPECT_EQ(expected, + *WalletItems::LegalDocument::CreateLegalDocument(*dict)); +} + +TEST_F(WalletItemsTest, LegalDocumentUrl) { + WalletItems::LegalDocument legal_doc("doc_id", ASCIIToUTF16("display_name")); + EXPECT_EQ("https://wallet.google.com/legaldocument?docId=doc_id", + legal_doc.url().spec()); +} + +TEST_F(WalletItemsTest, LegalDocumentEmptyId) { + WalletItems::LegalDocument legal_doc(GURL("http://example.com"), + ASCIIToUTF16("display_name")); + EXPECT_TRUE(legal_doc.id().empty()); +} + +TEST_F(WalletItemsTest, CreateWalletItemsWithRequiredActions) { + SetUpDictionary(kWalletItemsWithRequiredActions); + + std::vector<RequiredAction> required_actions; + required_actions.push_back(SETUP_WALLET); + required_actions.push_back(CHOOSE_ANOTHER_INSTRUMENT_OR_ADDRESS); + required_actions.push_back(ACCEPT_TOS); + required_actions.push_back(GAIA_AUTH); + required_actions.push_back(UPDATE_EXPIRATION_DATE); + required_actions.push_back(UPGRADE_MIN_ADDRESS); + required_actions.push_back(PASSIVE_GAIA_AUTH); + required_actions.push_back(REQUIRE_PHONE_NUMBER); + + WalletItems expected(required_actions, + std::string(), + std::string(), + std::string(), + std::string()); + EXPECT_EQ(expected, *WalletItems::CreateWalletItems(*dict)); + + ASSERT_FALSE(required_actions.empty()); + required_actions.pop_back(); + WalletItems different_required_actions(required_actions, + std::string(), + std::string(), + std::string(), + std::string()); + EXPECT_NE(expected, different_required_actions); +} + +TEST_F(WalletItemsTest, CreateWalletItemsWithInvalidRequiredActions) { + SetUpDictionary(kWalletItemsWithInvalidRequiredActions); + EXPECT_EQ(NULL, WalletItems::CreateWalletItems(*dict).get()); +} + +TEST_F(WalletItemsTest, CreateWalletItemsMissingGoogleTransactionId) { + SetUpDictionary(kWalletItemsMissingGoogleTransactionId); + EXPECT_EQ(NULL, WalletItems::CreateWalletItems(*dict).get()); +} + +TEST_F(WalletItemsTest, CreateWalletItems) { + SetUpDictionary(std::string(kWalletItems) + std::string(kCloseJson)); + std::vector<RequiredAction> required_actions; + WalletItems expected(required_actions, + "google_transaction_id", + "default_instrument_id", + "default_address_id", + "obfuscated_gaia_id"); + + scoped_ptr<Address> billing_address(new Address("country_code", + ASCIIToUTF16("name"), + ASCIIToUTF16("address1"), + ASCIIToUTF16("address2"), + ASCIIToUTF16("city"), + ASCIIToUTF16("state"), + ASCIIToUTF16("postal_code"), + ASCIIToUTF16("phone_number"), + std::string())); + std::vector<base::string16> supported_currencies; + supported_currencies.push_back(ASCIIToUTF16("currency")); + scoped_ptr<WalletItems::MaskedInstrument> masked_instrument( + new WalletItems::MaskedInstrument(ASCIIToUTF16("descriptive_name"), + WalletItems::MaskedInstrument::VISA, + supported_currencies, + ASCIIToUTF16("last_four_digits"), + 12, + 2012, + billing_address.Pass(), + WalletItems::MaskedInstrument::VALID, + "object_id")); + expected.AddInstrument(masked_instrument.Pass()); + + scoped_ptr<Address> shipping_address( + new Address("country_name_code", + ASCIIToUTF16("recipient_name"), + ASCIIToUTF16("address_line_1"), + ASCIIToUTF16("address_line_2"), + ASCIIToUTF16("locality_name"), + ASCIIToUTF16("administrative_area_name"), + ASCIIToUTF16("postal_code_number"), + ASCIIToUTF16("phone_number"), + "id")); + expected.AddAddress(shipping_address.Pass()); + EXPECT_EQ(expected, *WalletItems::CreateWalletItems(*dict)); + + // Now try with a legal document as well. + SetUpDictionary(std::string(kWalletItems) + + std::string(kRequiredLegalDocument) + + std::string(kCloseJson)); + scoped_ptr<WalletItems::LegalDocument> legal_document( + new WalletItems::LegalDocument("doc_id", + ASCIIToUTF16("display_name"))); + expected.AddLegalDocument(legal_document.Pass()); + expected.AddLegalDocument( + WalletItems::LegalDocument::CreatePrivacyPolicyDocument()); + + EXPECT_EQ(expected, *WalletItems::CreateWalletItems(*dict)); +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/content/browser/wallet/wallet_service_url.cc b/components/autofill/content/browser/wallet/wallet_service_url.cc new file mode 100644 index 0000000..aa5cf99 --- /dev/null +++ b/components/autofill/content/browser/wallet/wallet_service_url.cc @@ -0,0 +1,156 @@ +// Copyright 2013 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 "components/autofill/content/browser/wallet/wallet_service_url.h" + +#include <string> + +#include "base/command_line.h" +#include "base/metrics/field_trial.h" +#include "components/autofill/common/autofill_switches.h" +#include "google_apis/gaia/gaia_urls.h" +#include "googleurl/src/gurl.h" +#include "net/base/url_util.h" + +namespace autofill { +namespace { + +const char kProdWalletServiceUrl[] = "https://wallet.google.com/"; + +// TODO(ahutter): Remove this once production is ready. +const char kSandboxWalletServiceUrl[] = + "https://payments-form-dogfood.sandbox.google.com/"; + +// TODO(ahutter): Remove this once production is ready. +const char kSandboxWalletSecureServiceUrl[] = + "https://wallet-web.sandbox.google.com/"; + +bool IsWalletProductionEnabled() { + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + return command_line.HasSwitch(switches::kWalletServiceUseProd) || + base::FieldTrialList::FindFullName("WalletProductionService") == "Yes"; +} + +GURL GetWalletHostUrl() { + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + std::string wallet_service_hostname = + command_line.GetSwitchValueASCII(switches::kWalletServiceUrl); + if (!wallet_service_hostname.empty()) + return GURL(wallet_service_hostname); + if (IsWalletProductionEnabled()) + return GURL(kProdWalletServiceUrl); + return GURL(kSandboxWalletServiceUrl); +} + +GURL GetBaseWalletUrl() { + return GetWalletHostUrl().Resolve("online/v2/"); +} + +GURL GetBaseAutocheckoutUrl() { + return GetBaseWalletUrl().Resolve("wallet/autocheckout/v1/"); +} + +GURL GetBaseSecureUrl() { + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + std::string wallet_secure_url = + command_line.GetSwitchValueASCII(switches::kWalletSecureServiceUrl); + if (!wallet_secure_url.empty()) + return GURL(wallet_secure_url); + if (IsWalletProductionEnabled()) + return GURL(kProdWalletServiceUrl); + return GURL(kSandboxWalletSecureServiceUrl); +} + +} // namespace + +namespace wallet { + +GURL GetGetWalletItemsUrl() { + return GetBaseAutocheckoutUrl().Resolve("getWalletItemsJwtless"); +} + +GURL GetGetFullWalletUrl() { + return GetBaseAutocheckoutUrl().Resolve("getFullWalletJwtless"); +} + +GURL GetManageInstrumentsUrl() { + return GetBaseSecureUrl().Resolve("manage/w/0/#paymentMethods:"); +} + +GURL GetManageAddressesUrl() { + return GetBaseSecureUrl().Resolve("manage/w/0/#settings:addresses"); +} + +GURL GetAcceptLegalDocumentsUrl() { + return GetBaseAutocheckoutUrl().Resolve("acceptLegalDocument"); +} + +GURL GetAuthenticateInstrumentUrl() { + return GetBaseAutocheckoutUrl().Resolve("authenticateInstrument"); +} + +GURL GetSendStatusUrl() { + return GetBaseAutocheckoutUrl().Resolve("reportStatus"); +} + +GURL GetSaveToWalletUrl() { + return GetBaseAutocheckoutUrl().Resolve("saveToWallet"); +} + +GURL GetPassiveAuthUrl() { + return GetBaseWalletUrl().Resolve("passiveauth"); +} + +GURL GetEncryptionUrl() { + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + // TODO(ahutter): Stop checking these switches once we switch over to prod. + if (IsWalletProductionEnabled() || + command_line.HasSwitch(switches::kWalletServiceUrl)) { + return GetWalletHostUrl().Resolve( + "online-secure/temporarydata/cvv?s7e=cvv"); + } else { + return GetBaseSecureUrl().Resolve( + "online-secure/temporarydata/cvv?s7e=cvv"); + } +} + +GURL GetEscrowUrl() { + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + // TODO(ahutter): Stop checking these switches once we switch over to prod. + if (IsWalletProductionEnabled() || + command_line.HasSwitch(switches::kWalletServiceUrl)) { + return GetBaseSecureUrl().Resolve("dehEfe?s7e=cardNumber%3Bcvv"); + } else { + return GetBaseSecureUrl().Resolve("checkout/dehEfe?s7e=cardNumber%3Bcvv"); + } +} + +GURL GetSignInUrl() { + GURL url(GaiaUrls::GetInstance()->service_login_url()); + url = net::AppendQueryParameter(url, "service", "toolbar"); + url = net::AppendQueryParameter(url, "nui", "1"); + url = net::AppendQueryParameter(url, + "continue", + GetSignInContinueUrl().spec()); + return url; +} + +// The continue url portion of the sign-in URL. +GURL GetSignInContinueUrl() { + return GetPassiveAuthUrl(); +} + +bool IsSignInContinueUrl(const GURL& url) { + GURL final_url = wallet::GetSignInContinueUrl(); + return url.SchemeIsSecure() && + url.host() == final_url.host() && + url.path() == final_url.path(); +} + +bool IsUsingProd() { + return GetWalletHostUrl() == GURL(kProdWalletServiceUrl); +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/content/browser/wallet/wallet_service_url.h b/components/autofill/content/browser/wallet/wallet_service_url.h new file mode 100644 index 0000000..a9a32fb --- /dev/null +++ b/components/autofill/content/browser/wallet/wallet_service_url.h @@ -0,0 +1,43 @@ +// Copyright 2013 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 COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_WALLET_SERVICE_URL_H_ +#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_WALLET_SERVICE_URL_H_ + +class GURL; + +namespace autofill { +namespace wallet { + +GURL GetGetWalletItemsUrl(); +GURL GetGetFullWalletUrl(); +GURL GetManageInstrumentsUrl(); +GURL GetManageAddressesUrl(); +GURL GetAcceptLegalDocumentsUrl(); +GURL GetAuthenticateInstrumentUrl(); +GURL GetEncryptionUrl(); +GURL GetEscrowUrl(); +GURL GetSendStatusUrl(); +GURL GetSaveToWalletUrl(); +GURL GetPassiveAuthUrl(); + +// URL to visit for presenting the user with a sign-in dialog. +GURL GetSignInUrl(); + +// The the URL to use as a continue parameter in the sign-in URL. +// A redirect to this URL will occur once sign-in is complete. +GURL GetSignInContinueUrl(); + +// Returns true if |url| is an acceptable variant of the sign-in continue +// url. Can be used for detection of navigation to the continue url. +bool IsSignInContinueUrl(const GURL& url); + +// Whether calls to Online Wallet are hitting the production server rather than +// a sandbox or some malicious endpoint. +bool IsUsingProd(); + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_WALLET_SERVICE_URL_H_ diff --git a/components/autofill/content/browser/wallet/wallet_service_url_unittest.cc b/components/autofill/content/browser/wallet/wallet_service_url_unittest.cc new file mode 100644 index 0000000..ec18062 --- /dev/null +++ b/components/autofill/content/browser/wallet/wallet_service_url_unittest.cc @@ -0,0 +1,65 @@ +// Copyright 2013 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/command_line.h" +#include "components/autofill/common/autofill_switches.h" +#include "components/autofill/content/browser/wallet/wallet_service_url.h" +#include "googleurl/src/gurl.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace autofill { +namespace wallet { + +TEST(WalletServiceUrl, CheckDefaultUrls) { + EXPECT_EQ("https://payments-form-dogfood.sandbox.google.com/online/v2/wallet/" + "autocheckout/v1/getWalletItemsJwtless", + GetGetWalletItemsUrl().spec()); + EXPECT_EQ("https://payments-form-dogfood.sandbox.google.com/online/v2/wallet/" + "autocheckout/v1/getFullWalletJwtless", + GetGetFullWalletUrl().spec()); + EXPECT_EQ("https://wallet-web.sandbox.google.com/manage/w/0/#paymentMethods:", + GetManageInstrumentsUrl().spec()); + EXPECT_EQ("https://wallet-web.sandbox.google.com/manage/w/0/" + "#settings:addresses", + GetManageAddressesUrl().spec()); + EXPECT_EQ("https://payments-form-dogfood.sandbox.google.com/online/v2/wallet/" + "autocheckout/v1/acceptLegalDocument", + GetAcceptLegalDocumentsUrl().spec()); + EXPECT_EQ("https://payments-form-dogfood.sandbox.google.com/online/v2/wallet/" + "autocheckout/v1/authenticateInstrument", + GetAuthenticateInstrumentUrl().spec()); + EXPECT_EQ("https://payments-form-dogfood.sandbox.google.com/online/v2/wallet/" + "autocheckout/v1/reportStatus", + GetSendStatusUrl().spec()); + EXPECT_EQ("https://payments-form-dogfood.sandbox.google.com/online/v2/wallet/" + "autocheckout/v1/saveToWallet", + GetSaveToWalletUrl().spec()); + EXPECT_EQ("https://payments-form-dogfood.sandbox.google.com/online/v2/" + "passiveauth", + GetPassiveAuthUrl().spec()); + EXPECT_EQ("https://wallet-web.sandbox.google.com/online-secure/" + "temporarydata/cvv?s7e=cvv", + GetEncryptionUrl().spec()); + EXPECT_EQ("https://wallet-web.sandbox.google.com/checkout/dehEfe?" + "s7e=cardNumber%3Bcvv", + GetEscrowUrl().spec()); +} + +TEST(WalletServiceUrl, IsUsingProd) { + // The sandbox servers are the default (for now). Update if this changes. + EXPECT_FALSE(IsUsingProd()); + + CommandLine* command_line = CommandLine::ForCurrentProcess(); + command_line->AppendSwitch(switches::kWalletServiceUseProd); + EXPECT_TRUE(IsUsingProd()); + + const GURL prod_get_items_url = GetGetWalletItemsUrl(); + command_line->AppendSwitchASCII(switches::kWalletServiceUrl, "http://goo.gl"); + EXPECT_FALSE(IsUsingProd()); + + ASSERT_NE(prod_get_items_url, GetGetWalletItemsUrl()); +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/content/browser/wallet/wallet_signin_helper.cc b/components/autofill/content/browser/wallet/wallet_signin_helper.cc new file mode 100644 index 0000000..a78011a --- /dev/null +++ b/components/autofill/content/browser/wallet/wallet_signin_helper.cc @@ -0,0 +1,298 @@ +// Copyright 2013 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 "components/autofill/content/browser/wallet/wallet_signin_helper.h" + +#include "base/callback_helpers.h" +#include "base/json/json_reader.h" +#include "base/logging.h" +#include "base/rand_util.h" +#include "base/stringprintf.h" +#include "base/time.h" +#include "base/values.h" +#include "components/autofill/content/browser/wallet/wallet_service_url.h" +#include "components/autofill/content/browser/wallet/wallet_signin_helper_delegate.h" +#include "google_apis/gaia/gaia_auth_fetcher.h" +#include "google_apis/gaia/gaia_auth_util.h" +#include "google_apis/gaia/gaia_constants.h" +#include "google_apis/gaia/gaia_urls.h" +#include "google_apis/gaia/google_service_auth_error.h" +#include "net/base/escape.h" +#include "net/url_request/url_fetcher.h" +#include "net/url_request/url_request_context.h" + +namespace autofill { +namespace wallet { + +namespace { + +// Toolbar::GetAccountInfo API URL (JSON). +const char kGetAccountInfoUrlFormat[] = + "https://clients1.google.com/tbproxy/getaccountinfo?key=%d&rv=2"; + +} // namespace + +WalletSigninHelper::WalletSigninHelper( + WalletSigninHelperDelegate* delegate, + net::URLRequestContextGetter* getter) + : delegate_(delegate), + getter_(getter), + state_(IDLE) { + DCHECK(delegate_); +} + +WalletSigninHelper::~WalletSigninHelper() { +} + +void WalletSigninHelper::StartPassiveSignin() { + DCHECK_EQ(IDLE, state_); + DCHECK(!url_fetcher_); + DCHECK(!gaia_fetcher_); + + state_ = PASSIVE_EXECUTING_SIGNIN; + sid_.clear(); + lsid_.clear(); + username_.clear(); + const GURL& url = wallet::GetPassiveAuthUrl(); + url_fetcher_.reset(net::URLFetcher::Create( + 0, url, net::URLFetcher::GET, this)); + url_fetcher_->SetRequestContext(getter_); + url_fetcher_->Start(); +} + +void WalletSigninHelper::StartAutomaticSignin( + const std::string& sid, const std::string& lsid) { + DCHECK(!sid.empty()); + DCHECK(!lsid.empty()); + DCHECK_EQ(state_, IDLE); + DCHECK(!url_fetcher_); + DCHECK(!gaia_fetcher_); + + state_ = AUTOMATIC_FETCHING_USERINFO; + sid_ = sid; + lsid_ = lsid; + username_.clear(); + gaia_fetcher_.reset(new GaiaAuthFetcher( + this, GaiaConstants::kChromeSource, getter_)); + gaia_fetcher_->StartGetUserInfo(lsid_); +} + +void WalletSigninHelper::StartUserNameFetch() { + DCHECK_EQ(state_, IDLE); + DCHECK(!url_fetcher_); + DCHECK(!gaia_fetcher_); + + state_ = USERNAME_FETCHING_USERINFO; + sid_.clear(); + lsid_.clear(); + username_.clear(); + StartFetchingUserNameFromSession(); +} + +std::string WalletSigninHelper::GetGetAccountInfoUrlForTesting() const { + return base::StringPrintf(kGetAccountInfoUrlFormat, 0); +} + +void WalletSigninHelper::OnServiceError(const GoogleServiceAuthError& error) { + const State state_with_error = state_; + state_ = IDLE; + url_fetcher_.reset(); + gaia_fetcher_.reset(); + + switch(state_with_error) { + case IDLE: + NOTREACHED(); + break; + + case PASSIVE_EXECUTING_SIGNIN: /*FALLTHROUGH*/ + case PASSIVE_FETCHING_USERINFO: + delegate_->OnPassiveSigninFailure(error); + break; + + case AUTOMATIC_FETCHING_USERINFO: /*FALLTHROUGH*/ + case AUTOMATIC_ISSUING_AUTH_TOKEN: /*FALLTHROUGH*/ + case AUTOMATIC_EXECUTING_SIGNIN: + delegate_->OnAutomaticSigninFailure(error); + break; + + case USERNAME_FETCHING_USERINFO: + delegate_->OnUserNameFetchFailure(error); + break; + } +} + +void WalletSigninHelper::OnOtherError() { + OnServiceError(GoogleServiceAuthError::AuthErrorNone()); +} + +void WalletSigninHelper::OnGetUserInfoSuccess( + const UserInfoMap& data) { + DCHECK_EQ(AUTOMATIC_FETCHING_USERINFO, state_); + + UserInfoMap::const_iterator email_iter = + data.find(GaiaConstants::kClientOAuthEmailKey); + if (email_iter != data.end()) { + username_ = email_iter->second; + DCHECK(!url_fetcher_); + state_ = AUTOMATIC_ISSUING_AUTH_TOKEN; + gaia_fetcher_.reset(new GaiaAuthFetcher( + this, GaiaConstants::kChromeSource, getter_)); + gaia_fetcher_->StartIssueAuthToken( + sid_, lsid_, GaiaConstants::kGaiaService); + } else { + LOG(ERROR) << "GetUserInfoFailure: email field not found"; + OnOtherError(); + } +} + +void WalletSigninHelper::OnGetUserInfoFailure( + const GoogleServiceAuthError& error) { + LOG(ERROR) << "GetUserInfoFailure: " << error.ToString(); + DCHECK_EQ(AUTOMATIC_FETCHING_USERINFO, state_); + OnServiceError(error); +} + +void WalletSigninHelper::OnIssueAuthTokenSuccess( + const std::string& service, + const std::string& auth_token) { + DCHECK_EQ(AUTOMATIC_ISSUING_AUTH_TOKEN, state_); + + state_ = AUTOMATIC_EXECUTING_SIGNIN; + std::string encoded_auth_token = net::EscapeUrlEncodedData(auth_token, true); + std::string encoded_continue_url = + net::EscapeUrlEncodedData(wallet::GetPassiveAuthUrl().spec(), true); + std::string encoded_source = net::EscapeUrlEncodedData( + GaiaConstants::kChromeSource, true); + std::string body = base::StringPrintf( + "auth=%s&" + "continue=%s&" + "source=%s", + encoded_auth_token.c_str(), + encoded_continue_url.c_str(), + encoded_source.c_str()); + + gaia_fetcher_.reset(); + DCHECK(!url_fetcher_); + url_fetcher_.reset(net::URLFetcher::Create( + 0, + GURL(GaiaUrls::GetInstance()->token_auth_url()), + net::URLFetcher::POST, + this)); + url_fetcher_->SetUploadData("application/x-www-form-urlencoded", body); + url_fetcher_->SetRequestContext(getter_); + url_fetcher_->Start(); // This will result in OnURLFetchComplete callback. +} + +void WalletSigninHelper::OnIssueAuthTokenFailure( + const std::string& service, + const GoogleServiceAuthError& error) { + LOG(ERROR) << "IssueAuthTokenFailure: " << error.ToString(); + DCHECK_EQ(AUTOMATIC_ISSUING_AUTH_TOKEN, state_); + OnServiceError(error); +} + +void WalletSigninHelper::OnURLFetchComplete( + const net::URLFetcher* fetcher) { + DCHECK_EQ(url_fetcher_.get(), fetcher); + DCHECK(!gaia_fetcher_); + if (!fetcher->GetStatus().is_success() || + fetcher->GetResponseCode() < 200 || + fetcher->GetResponseCode() >= 300) { + LOG(ERROR) << "URLFetchFailure: state=" << state_ + << " r=" << fetcher->GetResponseCode() + << " s=" << fetcher->GetStatus().status() + << " e=" << fetcher->GetStatus().error(); + OnOtherError(); + return; + } + + switch (state_) { + case USERNAME_FETCHING_USERINFO: /*FALLTHROUGH*/ + case PASSIVE_FETCHING_USERINFO: + ProcessGetAccountInfoResponseAndFinish(); + break; + + case PASSIVE_EXECUTING_SIGNIN: + url_fetcher_.reset(); + state_ = PASSIVE_FETCHING_USERINFO; + StartFetchingUserNameFromSession(); + break; + + case AUTOMATIC_EXECUTING_SIGNIN: + state_ = IDLE; + url_fetcher_.reset(); + delegate_->OnAutomaticSigninSuccess(username_); + break; + + default: + NOTREACHED() << "unexpected state_=" << state_; + } +} + +void WalletSigninHelper::StartFetchingUserNameFromSession() { + DCHECK(!gaia_fetcher_); + const int random_number = static_cast<int>(base::RandUint64() % INT_MAX); + url_fetcher_.reset( + net::URLFetcher::Create( + 0, + GURL(base::StringPrintf(kGetAccountInfoUrlFormat, random_number)), + net::URLFetcher::GET, + this)); + url_fetcher_->SetRequestContext(getter_); + url_fetcher_->Start(); // This will result in OnURLFetchComplete callback. +} + +void WalletSigninHelper::ProcessGetAccountInfoResponseAndFinish() { + std::string email; + if (!ParseGetAccountInfoResponse(url_fetcher_.get(), &email)) { + LOG(ERROR) << "failed to get the user email"; + OnOtherError(); + return; + } + + username_ = email; + const State finishing_state = state_; + state_ = IDLE; + url_fetcher_.reset(); + switch(finishing_state) { + case USERNAME_FETCHING_USERINFO: + delegate_->OnUserNameFetchSuccess(username_); + break; + + case PASSIVE_FETCHING_USERINFO: + delegate_->OnPassiveSigninSuccess(username_); + break; + + default: + NOTREACHED() << "unexpected state_=" << finishing_state; + } +} + +bool WalletSigninHelper::ParseGetAccountInfoResponse( + const net::URLFetcher* fetcher, std::string* email) { + DCHECK(email); + + std::string data; + if (!fetcher->GetResponseAsString(&data)) { + LOG(ERROR) << "failed to GetResponseAsString"; + return false; + } + + scoped_ptr<base::Value> value(base::JSONReader::Read(data)); + if (!value.get() || value->GetType() != base::Value::TYPE_DICTIONARY) { + LOG(ERROR) << "failed to parse JSON response"; + return false; + } + + DictionaryValue* dict = static_cast<DictionaryValue*>(value.get()); + if (!dict->GetStringWithoutPathExpansion("email", email)) { + LOG(ERROR) << "no email in JSON response"; + return false; + } + + return !email->empty(); +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/content/browser/wallet/wallet_signin_helper.h b/components/autofill/content/browser/wallet/wallet_signin_helper.h new file mode 100644 index 0000000..4733dc9 --- /dev/null +++ b/components/autofill/content/browser/wallet/wallet_signin_helper.h @@ -0,0 +1,146 @@ +// Copyright 2013 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 COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_WALLET_SIGNIN_HELPER_H_ +#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_WALLET_SIGNIN_HELPER_H_ + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "google_apis/gaia/gaia_auth_consumer.h" +#include "net/url_request/url_fetcher_delegate.h" + +namespace net { +class URLFetcher; +class URLRequestContextGetter; +class URLRequestStatus; +} + +class GaiaAuthFetcher; +class GoogleServiceAuthError; + +namespace autofill { +namespace wallet { + +class WalletSigninHelperDelegate; + +// Authenticates the user against the Online Wallet service. +// This class is not thread-safe. An instance may be used on any thread, but +// should not be accessed from multiple threads. +class WalletSigninHelper : public GaiaAuthConsumer, + public net::URLFetcherDelegate { + public: + // Constructs a helper that works with a given |delegate| and uses + // a given |getter| to obtain a context for URL and GAIA fetchers. + // Both |delegate| and |getter| shall remain valid over the entire + // lifetime of the created instance. + WalletSigninHelper(WalletSigninHelperDelegate* delegate, + net::URLRequestContextGetter* getter); + + virtual ~WalletSigninHelper(); + + // Initiates an attempt to passively sign the user into the Online Wallet. + // A passive sign-in is a non-interactive refresh of content area cookies, + // and it succeeds as long as the Online Wallet service could safely accept + // or refresh the existing area cookies, and the user doesn't need to be + // fully reauthenticated with the service. + // Either OnPassiveSigninSuccess or OnPassiveSigninFailure will be called + // on the original thread. + void StartPassiveSignin(); + + // Initiates an attempt to automatically sign in a user into the service + // given the SID and LSID tokens obtained from the platform GAIA service. + // Please refer to GaiaAuthFetcher documentation for more details. + // Either OnAutomaticSigninSuccess or OnAutomaticSigninFailure will be called + // on the original thread. + void StartAutomaticSignin(const std::string& sid, const std::string& lsid); + + // Initiates a fetch of the user name of a signed-in user. + // Either OnUserNameFetchSuccess or OnUserNameFetchFailure will + // be called on the original thread. + void StartUserNameFetch(); + + protected: + // Sign-in helper states (for tests). + enum State { + IDLE, + PASSIVE_EXECUTING_SIGNIN, + PASSIVE_FETCHING_USERINFO, + AUTOMATIC_FETCHING_USERINFO, + AUTOMATIC_ISSUING_AUTH_TOKEN, + AUTOMATIC_EXECUTING_SIGNIN, + USERNAME_FETCHING_USERINFO, + }; + + // (For tests) Current state of the sign-in helper. + State state() const { return state_; } + + // (For tests) URL used to fetch the currently signed-in user info. + std::string GetGetAccountInfoUrlForTesting() const; + + private: + // Called if a service authentication error occurs. + void OnServiceError(const GoogleServiceAuthError& error); + + // Called if any other error occurs. + void OnOtherError(); + + // GaiaAuthConsumer implementation. + virtual void OnGetUserInfoSuccess(const UserInfoMap& data) OVERRIDE; + virtual void OnGetUserInfoFailure( + const GoogleServiceAuthError& error) OVERRIDE; + virtual void OnIssueAuthTokenSuccess( + const std::string& service, + const std::string& auth_token) OVERRIDE; + virtual void OnIssueAuthTokenFailure( + const std::string& service, + const GoogleServiceAuthError& error) OVERRIDE; + + // URLFetcherDelegate implementation. + virtual void OnURLFetchComplete(const net::URLFetcher* fetcher) OVERRIDE; + + // Initiates fetching of the currently signed-in user information. + void StartFetchingUserNameFromSession(); + + // Processes the user information received from the server by url_fetcher_ + // and calls the delegate callbacks on success/failure. + void ProcessGetAccountInfoResponseAndFinish(); + + // Attempts to parse the GetAccountInfo response from the server. + // Returns true on success; the obtained email address is stored into |email|. + bool ParseGetAccountInfoResponse(const net::URLFetcher* fetcher, + std::string* email); + + // Should be valid throughout the lifetime of the instance. + WalletSigninHelperDelegate* const delegate_; + + // URLRequestContextGetter to be used for URLFetchers. + net::URLRequestContextGetter* const getter_; + + // While passive login/merge session URL fetches are going on: + scoped_ptr<net::URLFetcher> url_fetcher_; + + // While Gaia authentication/userinfo fetches are going on: + scoped_ptr<GaiaAuthFetcher> gaia_fetcher_; + + // SID from StartAutomaticSignin(). + std::string sid_; + + // LSID from StartAutomaticSignin(). + std::string lsid_; + + // User account name (email) fetched from OnGetUserInfoSuccess(). + std::string username_; + + // Current internal state of the helper. + State state_; + + DISALLOW_COPY_AND_ASSIGN(WalletSigninHelper); +}; + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_WALLET_SIGNIN_HELPER_H_ diff --git a/components/autofill/content/browser/wallet/wallet_signin_helper_delegate.h b/components/autofill/content/browser/wallet/wallet_signin_helper_delegate.h new file mode 100644 index 0000000..47c5b66d --- /dev/null +++ b/components/autofill/content/browser/wallet/wallet_signin_helper_delegate.h @@ -0,0 +1,48 @@ +// Copyright 2013 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_UI_AUTOFILL_WALLET_SIGNIN_HELPER_DELEGATE_H_ +#define CHROME_BROWSER_UI_AUTOFILL_WALLET_SIGNIN_HELPER_DELEGATE_H_ + +#include <string> + +class GoogleServiceAuthError; + +namespace autofill { +namespace wallet { + +// An interface that defines the callbacks for objects that +// WalletSigninHelper can return data to. +class WalletSigninHelperDelegate { + public: + virtual ~WalletSigninHelperDelegate() {} + + // Called on a successful passive sign-in. + // |username| is the signed-in user account name (email). + virtual void OnPassiveSigninSuccess(const std::string& username) = 0; + + // Called on a failed passive sign-in; |error| describes the error. + virtual void OnPassiveSigninFailure(const GoogleServiceAuthError& error) = 0; + + // Called on a successful automatic sign-in. + // |username| is the signed-in user account name (email). + virtual void OnAutomaticSigninSuccess(const std::string& username) = 0; + + // Called on a failed automatic sign-in; |error| describes the error. + virtual void OnAutomaticSigninFailure( + const GoogleServiceAuthError& error) = 0; + + // Called on a successful fetch of the signed-in account name. + // |username| is the signed-in user account name (email). + virtual void OnUserNameFetchSuccess(const std::string& username) = 0; + + // Called on a failed fetch of the signed-in account name. + // |error| described the error. + virtual void OnUserNameFetchFailure(const GoogleServiceAuthError& error) = 0; +}; + +} // namespace wallet +} // namespace autofill + +#endif // CHROME_BROWSER_UI_AUTOFILL_WALLET_SIGNIN_HELPER_DELEGATE_H_ diff --git a/components/autofill/content/browser/wallet/wallet_signin_helper_unittest.cc b/components/autofill/content/browser/wallet/wallet_signin_helper_unittest.cc new file mode 100644 index 0000000..ea4b444f --- /dev/null +++ b/components/autofill/content/browser/wallet/wallet_signin_helper_unittest.cc @@ -0,0 +1,253 @@ +// Copyright 2013 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 "components/autofill/content/browser/wallet/wallet_signin_helper.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/stringprintf.h" +#include "chrome/test/base/testing_profile.h" +#include "components/autofill/content/browser/wallet/wallet_service_url.h" +#include "components/autofill/content/browser/wallet/wallet_signin_helper_delegate.h" +#include "content/public/test/test_browser_thread.h" +#include "google_apis/gaia/gaia_constants.h" +#include "google_apis/gaia/gaia_urls.h" +#include "google_apis/gaia/google_service_auth_error.h" +#include "net/url_request/test_url_fetcher_factory.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_context_getter.h" +#include "net/url_request/url_request_status.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using content::BrowserThread; +using testing::_; + +namespace autofill { +namespace wallet { + +namespace { + +const char kGetTokenPairValidResponse[] = + "{" + " \"refresh_token\": \"rt1\"," + " \"access_token\": \"at1\"," + " \"expires_in\": 3600," + " \"token_type\": \"Bearer\"" + "}"; + +const char kGetAccountInfoValidResponseFormat[] = + "{" + " \"email\": \"%s\"" + "}"; + +class MockWalletSigninHelperDelegate : public WalletSigninHelperDelegate { + public: + MOCK_METHOD1(OnPassiveSigninSuccess, void(const std::string& username)); + MOCK_METHOD1(OnAutomaticSigninSuccess, void(const std::string& username)); + MOCK_METHOD1(OnUserNameFetchSuccess, void(const std::string& username)); + MOCK_METHOD1(OnPassiveSigninFailure, + void(const GoogleServiceAuthError& error)); + MOCK_METHOD1(OnAutomaticSigninFailure, + void(const GoogleServiceAuthError& error)); + MOCK_METHOD1(OnUserNameFetchFailure, + void(const GoogleServiceAuthError& error)); +}; + +class WalletSigninHelperForTesting : public WalletSigninHelper { + public: + WalletSigninHelperForTesting(WalletSigninHelperDelegate* delegate, + net::URLRequestContextGetter* getter) + : WalletSigninHelper(delegate, getter) { + } + + // Bring in the test-only getters. + using WalletSigninHelper::GetGetAccountInfoUrlForTesting; + using WalletSigninHelper::state; + + // Bring in the State enum. + using WalletSigninHelper::State; + using WalletSigninHelper::IDLE; +}; + +} // namespace + +class WalletSigninHelperTest : public testing::Test { + public: + WalletSigninHelperTest() : io_thread_(content::BrowserThread::IO) {} + + virtual void SetUp() OVERRIDE { + io_thread_.StartIOThread(); + profile_.CreateRequestContext(); + signin_helper_.reset(new WalletSigninHelperForTesting( + &mock_delegate_, + profile_.GetRequestContext())); + EXPECT_EQ(WalletSigninHelperForTesting::IDLE, state()); + } + + virtual void TearDown() OVERRIDE { + signin_helper_.reset(); + profile_.ResetRequestContext(); + io_thread_.Stop(); + } + + protected: + // Sets up a response for the mock URLFetcher and completes the request. + void SetUpFetcherResponseAndCompleteRequest( + const std::string& url, + int response_code, + const net::ResponseCookies& cookies, + const std::string& response_string) { + net::TestURLFetcher* fetcher = factory_.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + ASSERT_TRUE(fetcher->delegate()); + + fetcher->set_url(GURL(url)); + fetcher->set_status(net::URLRequestStatus()); + fetcher->set_response_code(response_code); + fetcher->SetResponseString(response_string); + fetcher->set_cookies(cookies); + fetcher->delegate()->OnURLFetchComplete(fetcher); + } + + void MockSuccessfulOAuthLoginResponse() { + SetUpFetcherResponseAndCompleteRequest( + GaiaUrls::GetInstance()->client_login_url(), 200, + net::ResponseCookies(), + "SID=sid\nLSID=lsid\nAuth=auth"); + } + + void MockFailedOAuthLoginResponse404() { + SetUpFetcherResponseAndCompleteRequest( + GaiaUrls::GetInstance()->client_login_url(), + 404, + net::ResponseCookies(), + std::string()); + } + + void MockSuccessfulGaiaUserInfoResponse(const std::string& username) { + SetUpFetcherResponseAndCompleteRequest( + GaiaUrls::GetInstance()->get_user_info_url(), 200, + net::ResponseCookies(), + "email=" + username); + } + + void MockFailedGaiaUserInfoResponse404() { + SetUpFetcherResponseAndCompleteRequest( + GaiaUrls::GetInstance()->get_user_info_url(), + 404, + net::ResponseCookies(), + std::string()); + } + + void MockSuccessfulGetAccountInfoResponse(const std::string& username) { + SetUpFetcherResponseAndCompleteRequest( + signin_helper_->GetGetAccountInfoUrlForTesting(), 200, + net::ResponseCookies(), + base::StringPrintf( + kGetAccountInfoValidResponseFormat, + username.c_str())); + } + + void MockFailedGetAccountInfoResponse404() { + SetUpFetcherResponseAndCompleteRequest( + signin_helper_->GetGetAccountInfoUrlForTesting(), + 404, + net::ResponseCookies(), + std::string()); + } + + void MockSuccessfulPassiveAuthUrlMergeAndRedirectResponse() { + SetUpFetcherResponseAndCompleteRequest(wallet::GetPassiveAuthUrl().spec(), + 200, + net::ResponseCookies(), + std::string()); + } + + void MockFailedPassiveAuthUrlMergeAndRedirectResponse404() { + SetUpFetcherResponseAndCompleteRequest(wallet::GetPassiveAuthUrl().spec(), + 404, + net::ResponseCookies(), + std::string()); + } + + WalletSigninHelperForTesting::State state() const { + return signin_helper_->state(); + } + + scoped_ptr<WalletSigninHelperForTesting> signin_helper_; + MockWalletSigninHelperDelegate mock_delegate_; + + private: + // The profile's request context must be released on the IO thread. + content::TestBrowserThread io_thread_; + net::TestURLFetcherFactory factory_; + TestingProfile profile_; +}; + +TEST_F(WalletSigninHelperTest, PassiveSigninSuccessful) { + EXPECT_CALL(mock_delegate_, OnPassiveSigninSuccess("user@gmail.com")); + signin_helper_->StartPassiveSignin(); + MockSuccessfulPassiveAuthUrlMergeAndRedirectResponse(); + MockSuccessfulGetAccountInfoResponse("user@gmail.com"); +} + +TEST_F(WalletSigninHelperTest, PassiveSigninFailedSignin) { + EXPECT_CALL(mock_delegate_, OnPassiveSigninFailure(_)); + signin_helper_->StartPassiveSignin(); + MockFailedPassiveAuthUrlMergeAndRedirectResponse404(); +} + +TEST_F(WalletSigninHelperTest, PassiveSigninFailedUserInfo) { + EXPECT_CALL(mock_delegate_, OnPassiveSigninFailure(_)); + signin_helper_->StartPassiveSignin(); + MockSuccessfulPassiveAuthUrlMergeAndRedirectResponse(); + MockFailedGetAccountInfoResponse404(); +} + +TEST_F(WalletSigninHelperTest, PassiveUserInfoSuccessful) { + EXPECT_CALL(mock_delegate_, OnUserNameFetchSuccess("user@gmail.com")); + signin_helper_->StartUserNameFetch(); + MockSuccessfulGetAccountInfoResponse("user@gmail.com"); +} + +TEST_F(WalletSigninHelperTest, PassiveUserInfoFailedUserInfo) { + EXPECT_CALL(mock_delegate_, OnUserNameFetchFailure(_)); + signin_helper_->StartUserNameFetch(); + MockFailedGetAccountInfoResponse404(); +} + +TEST_F(WalletSigninHelperTest, AutomaticSigninSuccessful) { + EXPECT_CALL(mock_delegate_, OnAutomaticSigninSuccess("user@gmail.com")); + signin_helper_->StartAutomaticSignin("123SID", "123LSID"); + MockSuccessfulGaiaUserInfoResponse("user@gmail.com"); + MockSuccessfulOAuthLoginResponse(); + MockSuccessfulPassiveAuthUrlMergeAndRedirectResponse(); +} + +TEST_F(WalletSigninHelperTest, AutomaticSigninFailedGetUserInfo) { + EXPECT_CALL(mock_delegate_, OnAutomaticSigninFailure(_)); + signin_helper_->StartAutomaticSignin("123SID", "123LSID"); + MockFailedGaiaUserInfoResponse404(); +} + +TEST_F(WalletSigninHelperTest, AutomaticSigninFailedOAuthLogin) { + EXPECT_CALL(mock_delegate_, OnAutomaticSigninFailure(_)); + signin_helper_->StartAutomaticSignin("123SID", "123LSID"); + MockSuccessfulGaiaUserInfoResponse("user@gmail.com"); + MockFailedOAuthLoginResponse404(); +} + +TEST_F(WalletSigninHelperTest, AutomaticSigninFailedSignin) { + EXPECT_CALL(mock_delegate_, OnAutomaticSigninFailure(_)); + signin_helper_->StartAutomaticSignin("123SID", "123LSID"); + MockSuccessfulGaiaUserInfoResponse("user@gmail.com"); + MockSuccessfulOAuthLoginResponse(); + MockFailedPassiveAuthUrlMergeAndRedirectResponse404(); +} + +// TODO(aruslan): http://crbug.com/188317 Need more tests. + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/content/browser/wallet/wallet_test_util.cc b/components/autofill/content/browser/wallet/wallet_test_util.cc new file mode 100644 index 0000000..8b5cf8f --- /dev/null +++ b/components/autofill/content/browser/wallet/wallet_test_util.cc @@ -0,0 +1,188 @@ +// Copyright 2013 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 "components/autofill/content/browser/wallet/wallet_test_util.h" + +#include <string> +#include <vector> + +#include "base/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time.h" +#include "base/values.h" +#include "components/autofill/content/browser/wallet/full_wallet.h" +#include "components/autofill/content/browser/wallet/instrument.h" +#include "components/autofill/content/browser/wallet/required_action.h" +#include "components/autofill/content/browser/wallet/wallet_address.h" + +namespace autofill { +namespace wallet { + +namespace { + +int FutureYear() { + // "In the Year 3000." - Richie "LaBamba" Rosenberg + return 3000; +} + +} // namespace + +scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrumentWithDetails( + const std::string& id, + scoped_ptr<Address> address, + WalletItems::MaskedInstrument::Type type, + WalletItems::MaskedInstrument::Status status) { + return scoped_ptr<WalletItems::MaskedInstrument>( + new WalletItems::MaskedInstrument(ASCIIToUTF16("descriptive_name"), + type, + std::vector<base::string16>(), + ASCIIToUTF16("1111"), + 12, + FutureYear(), + address.Pass(), + status, + id)); +} + +scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrumentWithId( + const std::string& id) { + return GetTestMaskedInstrumentWithDetails( + id, + GetTestAddress(), + WalletItems::MaskedInstrument::VISA, + WalletItems::MaskedInstrument::VALID); +} + +scoped_ptr<WalletItems::MaskedInstrument> +GetTestMaskedInstrumentWithIdAndAddress( + const std::string& id, scoped_ptr<Address> address) { + return GetTestMaskedInstrumentWithDetails( + id, + address.Pass(), + WalletItems::MaskedInstrument::VISA, + WalletItems::MaskedInstrument::VALID); +} + +scoped_ptr<Address> GetTestAddress() { + return scoped_ptr<Address>(new Address("US", + ASCIIToUTF16("recipient_name"), + ASCIIToUTF16("address_line_1"), + ASCIIToUTF16("address_line_2"), + ASCIIToUTF16("locality_name"), + ASCIIToUTF16("admin_area_name"), + ASCIIToUTF16("postal_code_number"), + ASCIIToUTF16("phone_number"), + std::string())); +} + +scoped_ptr<Address> GetTestMinimalAddress() { + scoped_ptr<Address> address = GetTestAddress(); + address->set_is_complete_address(false); + return address.Pass(); +} + +scoped_ptr<FullWallet> GetTestFullWallet() { + base::Time::Exploded exploded; + base::Time::Now().LocalExplode(&exploded); + return scoped_ptr<FullWallet>(new FullWallet(FutureYear(), + 12, + "iin", + "rest", + GetTestAddress(), + GetTestShippingAddress(), + std::vector<RequiredAction>())); +} + +scoped_ptr<Instrument> GetTestInstrument() { + return scoped_ptr<Instrument>(new Instrument(ASCIIToUTF16("4444444444444448"), + ASCIIToUTF16("123"), + 12, + FutureYear(), + Instrument::VISA, + GetTestAddress())); +} + +scoped_ptr<WalletItems::LegalDocument> GetTestLegalDocument() { + base::DictionaryValue dict; + dict.SetString("legal_document_id", "document_id"); + dict.SetString("display_name", "display_name"); + return wallet::WalletItems::LegalDocument::CreateLegalDocument(dict); +} + +scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrument() { + return GetTestMaskedInstrumentWithId("default_instrument_id"); +} + +scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrumentExpired() { + return GetTestMaskedInstrumentWithDetails( + "default_instrument_id", + GetTestAddress(), + WalletItems::MaskedInstrument::VISA, + WalletItems::MaskedInstrument::EXPIRED); +} + +scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrumentInvalid() { + return GetTestMaskedInstrumentWithDetails( + "default_instrument_id", + GetTestAddress(), + WalletItems::MaskedInstrument::VISA, + WalletItems::MaskedInstrument::DECLINED); +} + +scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrumentAmex() { + return GetTestMaskedInstrumentWithDetails( + "default_instrument_id", + GetTestAddress(), + WalletItems::MaskedInstrument::AMEX, + // Amex cards are marked with status AMEX_NOT_SUPPORTED by the server. + WalletItems::MaskedInstrument::AMEX_NOT_SUPPORTED); +} + +scoped_ptr<WalletItems::MaskedInstrument> GetTestNonDefaultMaskedInstrument() { + return GetTestMaskedInstrumentWithId("instrument_id"); +} + +scoped_ptr<Address> GetTestSaveableAddress() { + return scoped_ptr<Address>(new Address( + "US", + ASCIIToUTF16("save_recipient_name"), + ASCIIToUTF16("save_address_line_1"), + ASCIIToUTF16("save_address_line_2"), + ASCIIToUTF16("save_locality_name"), + ASCIIToUTF16("save_admin_area_name"), + ASCIIToUTF16("save_postal_code_number"), + ASCIIToUTF16("save_phone_number"), + std::string())); +} + +scoped_ptr<Address> GetTestShippingAddress() { + return scoped_ptr<Address>(new Address( + "US", + ASCIIToUTF16("ship_recipient_name"), + ASCIIToUTF16("ship_address_line_1"), + ASCIIToUTF16("ship_address_line_2"), + ASCIIToUTF16("ship_locality_name"), + ASCIIToUTF16("ship_admin_area_name"), + ASCIIToUTF16("ship_postal_code_number"), + ASCIIToUTF16("ship_phone_number"), + "default_address_id")); +} + +scoped_ptr<Address> GetTestNonDefaultShippingAddress() { + scoped_ptr<Address> address = GetTestShippingAddress(); + address->set_object_id("address_id"); + return address.Pass(); +} + +scoped_ptr<WalletItems> GetTestWalletItems() { + return scoped_ptr<WalletItems>( + new wallet::WalletItems(std::vector<RequiredAction>(), + "google_transaction_id", + "default_instrument_id", + "default_address_id", + "obfuscated_gaia_id")); +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/content/browser/wallet/wallet_test_util.h b/components/autofill/content/browser/wallet/wallet_test_util.h new file mode 100644 index 0000000..e4978a6 --- /dev/null +++ b/components/autofill/content/browser/wallet/wallet_test_util.h @@ -0,0 +1,39 @@ +// Copyright 2013 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 COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_WALLET_TEST_UTIL_H_ +#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_WALLET_TEST_UTIL_H_ + +#include "base/memory/scoped_ptr.h" +#include "components/autofill/content/browser/wallet/wallet_items.h" + +namespace autofill { +namespace wallet { + +class Address; +class FullWallet; +class Instrument; + +scoped_ptr<Address> GetTestAddress(); +scoped_ptr<Address> GetTestMinimalAddress(); +scoped_ptr<FullWallet> GetTestFullWallet(); +scoped_ptr<Instrument> GetTestInstrument(); +scoped_ptr<WalletItems::LegalDocument> GetTestLegalDocument(); +scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrument(); +scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrumentExpired(); +scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrumentInvalid(); +scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrumentAmex(); +scoped_ptr<WalletItems::MaskedInstrument> GetTestNonDefaultMaskedInstrument(); +scoped_ptr<WalletItems::MaskedInstrument> + GetTestMaskedInstrumentWithIdAndAddress( + const std::string& id, scoped_ptr<Address> address); +scoped_ptr<Address> GetTestSaveableAddress(); +scoped_ptr<Address> GetTestShippingAddress(); +scoped_ptr<Address> GetTestNonDefaultShippingAddress(); +scoped_ptr<WalletItems> GetTestWalletItems(); + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_WALLET_TEST_UTIL_H_ |