| // 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 |