summaryrefslogtreecommitdiffstats
path: root/components/autofill/content/browser
diff options
context:
space:
mode:
Diffstat (limited to 'components/autofill/content/browser')
-rw-r--r--components/autofill/content/browser/wallet/OWNERS1
-rw-r--r--components/autofill/content/browser/wallet/encryption_escrow_client.cc189
-rw-r--r--components/autofill/content/browser/wallet/encryption_escrow_client.h100
-rw-r--r--components/autofill/content/browser/wallet/encryption_escrow_client_observer.h53
-rw-r--r--components/autofill/content/browser/wallet/encryption_escrow_client_unittest.cc204
-rw-r--r--components/autofill/content/browser/wallet/full_wallet.cc283
-rw-r--r--components/autofill/content/browser/wallet/full_wallet.h130
-rw-r--r--components/autofill/content/browser/wallet/full_wallet_unittest.cc505
-rw-r--r--components/autofill/content/browser/wallet/instrument.cc141
-rw-r--r--components/autofill/content/browser/wallet/instrument.h104
-rw-r--r--components/autofill/content/browser/wallet/instrument_unittest.cc188
-rw-r--r--components/autofill/content/browser/wallet/required_action.cc66
-rw-r--r--components/autofill/content/browser/wallet/required_action.h44
-rw-r--r--components/autofill/content/browser/wallet/wallet_address.cc315
-rw-r--r--components/autofill/content/browser/wallet/wallet_address.h200
-rw-r--r--components/autofill/content/browser/wallet/wallet_address_unittest.cc381
-rw-r--r--components/autofill/content/browser/wallet/wallet_client.cc975
-rw-r--r--components/autofill/content/browser/wallet/wallet_client.h323
-rw-r--r--components/autofill/content/browser/wallet/wallet_client_delegate.h112
-rw-r--r--components/autofill/content/browser/wallet/wallet_client_unittest.cc1744
-rw-r--r--components/autofill/content/browser/wallet/wallet_items.cc530
-rw-r--r--components/autofill/content/browser/wallet/wallet_items.h298
-rw-r--r--components/autofill/content/browser/wallet/wallet_items_unittest.cc570
-rw-r--r--components/autofill/content/browser/wallet/wallet_service_url.cc156
-rw-r--r--components/autofill/content/browser/wallet/wallet_service_url.h43
-rw-r--r--components/autofill/content/browser/wallet/wallet_service_url_unittest.cc65
-rw-r--r--components/autofill/content/browser/wallet/wallet_signin_helper.cc298
-rw-r--r--components/autofill/content/browser/wallet/wallet_signin_helper.h146
-rw-r--r--components/autofill/content/browser/wallet/wallet_signin_helper_delegate.h48
-rw-r--r--components/autofill/content/browser/wallet/wallet_signin_helper_unittest.cc253
-rw-r--r--components/autofill/content/browser/wallet/wallet_test_util.cc188
-rw-r--r--components/autofill/content/browser/wallet/wallet_test_util.h39
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, &currency))
+ supported_currencies.push_back(currency);
+ }
+ } else {
+ DVLOG(1) << "Response from Google Wallet missing supported currency";
+ }
+
+ int expiration_month;
+ if (!dictionary.GetInteger("expiration_month", &expiration_month))
+ DVLOG(1) << "Response from Google Wallet missing expiration month";
+
+ int expiration_year;
+ if (!dictionary.GetInteger("expiration_year", &expiration_year))
+ DVLOG(1) << "Response from Google Wallet missing expiration year";
+
+ 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_