| // Copyright 2015 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/core/browser/payments/payments_client.h" |
| |
| #include <memory> |
| #include <set> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/json/json_reader.h" |
| #include "base/json/json_writer.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "components/autofill/core/browser/autofill_experiments.h" |
| #include "components/autofill/core/browser/autofill_type.h" |
| #include "components/autofill/core/browser/data_model/autofill_data_model.h" |
| #include "components/autofill/core/browser/data_model/credit_card.h" |
| #include "components/autofill/core/browser/payments/account_info_getter.h" |
| #include "components/autofill/core/browser/payments/local_card_migration_manager.h" |
| #include "components/autofill/core/browser/payments/payments_request.h" |
| #include "components/autofill/core/browser/payments/payments_service_url.h" |
| #include "components/autofill/core/common/autofill_features.h" |
| #include "components/autofill/core/common/autofill_payments_features.h" |
| #include "components/signin/public/identity_manager/identity_manager.h" |
| #include "components/signin/public/identity_manager/primary_account_access_token_fetcher.h" |
| #include "components/variations/net/variations_http_headers.h" |
| #include "net/base/escape.h" |
| #include "net/base/load_flags.h" |
| #include "net/http/http_status_code.h" |
| #include "net/traffic_annotation/network_traffic_annotation.h" |
| #include "services/network/public/cpp/resource_request.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| #include "services/network/public/cpp/simple_url_loader.h" |
| |
| namespace autofill { |
| namespace payments { |
| |
| namespace { |
| |
| const char kGetUnmaskDetailsRequestPath[] = |
| "payments/apis/chromepaymentsservice/getdetailsforgetrealpan"; |
| |
| const char kUnmaskCardRequestPath[] = |
| "payments/apis-secure/creditcardservice/getrealpan?s7e_suffix=chromewallet"; |
| const char kUnmaskCardRequestFormat[] = |
| "requestContentType=application/json; charset=utf-8&request=%s" |
| "&s7e_13_cvc=%s"; |
| |
| const char kOptChangeRequestPath[] = |
| "payments/apis/chromepaymentsservice/autofillauthoptchange"; |
| |
| const char kGetUploadDetailsRequestPath[] = |
| "payments/apis/chromepaymentsservice/getdetailsforsavecard"; |
| |
| const char kUploadCardRequestPath[] = |
| "payments/apis-secure/chromepaymentsservice/savecard" |
| "?s7e_suffix=chromewallet"; |
| const char kUploadCardRequestFormat[] = |
| "requestContentType=application/json; charset=utf-8&request=%s" |
| "&s7e_1_pan=%s&s7e_13_cvc=%s"; |
| const char kUploadCardRequestFormatWithoutCvc[] = |
| "requestContentType=application/json; charset=utf-8&request=%s" |
| "&s7e_1_pan=%s"; |
| |
| const char kMigrateCardsRequestPath[] = |
| "payments/apis-secure/chromepaymentsservice/migratecards" |
| "?s7e_suffix=chromewallet"; |
| const char kMigrateCardsRequestFormat[] = |
| "requestContentType=application/json; charset=utf-8&request=%s"; |
| |
| const char kTokenFetchId[] = "wallet_client"; |
| const char kPaymentsOAuth2Scope[] = |
| "https://www.googleapis.com/auth/wallet.chrome"; |
| |
| GURL GetRequestUrl(const std::string& path) { |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch("sync-url")) { |
| if (IsPaymentsProductionEnabled()) { |
| LOG(ERROR) << "You are using production Payments but you specified a " |
| "--sync-url. You likely want to disable the sync sandbox " |
| "or switch to sandbox Payments. Both are controlled in " |
| "about:flags."; |
| } |
| } else if (!IsPaymentsProductionEnabled()) { |
| LOG(ERROR) << "You are using sandbox Payments but you didn't specify a " |
| "--sync-url. You likely want to enable the sync sandbox " |
| "or switch to production Payments. Both are controlled in " |
| "about:flags."; |
| } |
| |
| return GetBaseSecureUrl().Resolve(path); |
| } |
| |
| base::Value BuildCustomerContextDictionary(int64_t external_customer_id) { |
| base::Value customer_context(base::Value::Type::DICTIONARY); |
| customer_context.SetKey("external_customer_id", |
| base::Value(std::to_string(external_customer_id))); |
| return customer_context; |
| } |
| |
| base::Value BuildRiskDictionary(const std::string& encoded_risk_data) { |
| base::Value risk_data(base::Value::Type::DICTIONARY); |
| #if defined(OS_IOS) |
| // Browser fingerprinting is not available on iOS. Instead, we generate |
| // RiskAdvisoryData. |
| risk_data.SetKey("message_type", base::Value("RISK_ADVISORY_DATA")); |
| risk_data.SetKey("encoding_type", base::Value("BASE_64_URL")); |
| #else |
| risk_data.SetKey("message_type", |
| base::Value("BROWSER_NATIVE_FINGERPRINTING")); |
| risk_data.SetKey("encoding_type", base::Value("BASE_64")); |
| #endif |
| |
| risk_data.SetKey("value", base::Value(encoded_risk_data)); |
| |
| return risk_data; |
| } |
| |
| void SetStringIfNotEmpty(const AutofillDataModel& profile, |
| const ServerFieldType& type, |
| const std::string& app_locale, |
| const std::string& path, |
| base::Value& dictionary) { |
| const base::string16 value = profile.GetInfo(AutofillType(type), app_locale); |
| if (!value.empty()) |
| dictionary.SetKey(path, base::Value(value)); |
| } |
| |
| void AppendStringIfNotEmpty(const AutofillProfile& profile, |
| const ServerFieldType& type, |
| const std::string& app_locale, |
| base::Value& list) { |
| const base::string16 value = profile.GetInfo(type, app_locale); |
| if (!value.empty()) |
| list.Append(value); |
| } |
| |
| // Returns a dictionary with the structure expected by Payments RPCs, containing |
| // each of the fields in |profile|, formatted according to |app_locale|. If |
| // |include_non_location_data| is false, the name and phone number in |profile| |
| // are not included. |
| base::Value BuildAddressDictionary(const AutofillProfile& profile, |
| const std::string& app_locale, |
| bool include_non_location_data) { |
| base::Value postal_address(base::Value::Type::DICTIONARY); |
| |
| if (include_non_location_data) { |
| SetStringIfNotEmpty(profile, NAME_FULL, app_locale, |
| PaymentsClient::kRecipientName, postal_address); |
| } |
| |
| base::Value address_lines(base::Value::Type::LIST); |
| AppendStringIfNotEmpty(profile, ADDRESS_HOME_LINE1, app_locale, |
| address_lines); |
| AppendStringIfNotEmpty(profile, ADDRESS_HOME_LINE2, app_locale, |
| address_lines); |
| AppendStringIfNotEmpty(profile, ADDRESS_HOME_LINE3, app_locale, |
| address_lines); |
| if (!address_lines.GetList().empty()) |
| postal_address.SetKey("address_line", std::move(address_lines)); |
| |
| SetStringIfNotEmpty(profile, ADDRESS_HOME_CITY, app_locale, "locality_name", |
| postal_address); |
| SetStringIfNotEmpty(profile, ADDRESS_HOME_STATE, app_locale, |
| "administrative_area_name", postal_address); |
| SetStringIfNotEmpty(profile, ADDRESS_HOME_ZIP, app_locale, |
| "postal_code_number", postal_address); |
| |
| // Use GetRawInfo to get a country code instead of the country name: |
| const base::string16 country_code = profile.GetRawInfo(ADDRESS_HOME_COUNTRY); |
| if (!country_code.empty()) |
| postal_address.SetKey("country_name_code", base::Value(country_code)); |
| |
| base::Value address(base::Value::Type::DICTIONARY); |
| address.SetKey("postal_address", std::move(postal_address)); |
| |
| if (include_non_location_data) { |
| SetStringIfNotEmpty(profile, PHONE_HOME_WHOLE_NUMBER, app_locale, |
| PaymentsClient::kPhoneNumber, address); |
| } |
| |
| return address; |
| } |
| |
| // Returns a dictionary of the credit card with the structure expected by |
| // Payments RPCs, containing expiration month, expiration year and cardholder |
| // name (if any) fields in |credit_card|, formatted according to |app_locale|. |
| // |pan_field_name| is the field name for the encrypted pan. We use each credit |
| // card's guid as the unique id. |
| base::Value BuildCreditCardDictionary(const CreditCard& credit_card, |
| const std::string& app_locale, |
| const std::string& pan_field_name) { |
| base::Value card(base::Value::Type::DICTIONARY); |
| card.SetKey("unique_id", base::Value(credit_card.guid())); |
| |
| const base::string16 exp_month = |
| credit_card.GetInfo(AutofillType(CREDIT_CARD_EXP_MONTH), app_locale); |
| const base::string16 exp_year = credit_card.GetInfo( |
| AutofillType(CREDIT_CARD_EXP_4_DIGIT_YEAR), app_locale); |
| int value = 0; |
| if (base::StringToInt(exp_month, &value)) |
| card.SetKey("expiration_month", base::Value(value)); |
| if (base::StringToInt(exp_year, &value)) |
| card.SetKey("expiration_year", base::Value(value)); |
| SetStringIfNotEmpty(credit_card, CREDIT_CARD_NAME_FULL, app_locale, |
| "cardholder_name", card); |
| |
| card.SetKey("encrypted_pan", base::Value("__param:" + pan_field_name)); |
| return card; |
| } |
| |
| // Populates the list of active experiments that affect either the data sent in |
| // payments RPCs or whether the RPCs are sent or not. |
| void SetActiveExperiments(const std::vector<const char*>& active_experiments, |
| base::Value& request_dict) { |
| if (active_experiments.empty()) |
| return; |
| |
| base::Value active_chrome_experiments(base::Value::Type::LIST); |
| for (const char* it : active_experiments) |
| active_chrome_experiments.Append(it); |
| |
| request_dict.SetKey("active_chrome_experiments", |
| std::move(active_chrome_experiments)); |
| } |
| |
| class GetUnmaskDetailsRequest : public PaymentsRequest { |
| public: |
| GetUnmaskDetailsRequest(GetUnmaskDetailsCallback callback, |
| const std::string& app_locale, |
| const bool full_sync_enabled) |
| : callback_(std::move(callback)), |
| app_locale_(app_locale), |
| full_sync_enabled_(full_sync_enabled) {} |
| ~GetUnmaskDetailsRequest() override {} |
| |
| std::string GetRequestUrlPath() override { |
| return kGetUnmaskDetailsRequestPath; |
| } |
| |
| std::string GetRequestContentType() override { return "application/json"; } |
| |
| std::string GetRequestContent() override { |
| base::Value request_dict(base::Value::Type::DICTIONARY); |
| base::Value context(base::Value::Type::DICTIONARY); |
| context.SetKey("language_code", base::Value(app_locale_)); |
| context.SetKey("billable_service", |
| base::Value(kUnmaskCardBillableServiceNumber)); |
| request_dict.SetKey("context", std::move(context)); |
| |
| if (ShouldUseActiveSignedInAccount()) { |
| base::Value chrome_user_context(base::Value::Type::DICTIONARY); |
| chrome_user_context.SetKey("full_sync_enabled", |
| base::Value(full_sync_enabled_)); |
| request_dict.SetKey("chrome_user_context", |
| std::move(chrome_user_context)); |
| } |
| |
| std::string request_content; |
| base::JSONWriter::Write(request_dict, &request_content); |
| VLOG(3) << "getdetailsforgetrealpan request body: " << request_content; |
| return request_content; |
| } |
| |
| void ParseResponse(const base::Value& response) override { |
| const auto* method = response.FindStringKey("authentication_method"); |
| if (method) { |
| if (*method == "CVC") { |
| unmask_details_.unmask_auth_method = |
| AutofillClient::UnmaskAuthMethod::CVC; |
| } else if (*method == "FIDO") { |
| unmask_details_.unmask_auth_method = |
| AutofillClient::UnmaskAuthMethod::FIDO; |
| } |
| } |
| |
| const auto* offer_fido_opt_in = |
| response.FindKeyOfType("offer_fido_opt_in", base::Value::Type::BOOLEAN); |
| unmask_details_.offer_fido_opt_in = |
| offer_fido_opt_in && offer_fido_opt_in->GetBool(); |
| |
| const auto* dictionary_value = response.FindKeyOfType( |
| "fido_request_options", base::Value::Type::DICTIONARY); |
| if (dictionary_value) |
| unmask_details_.fido_request_options = dictionary_value->Clone(); |
| |
| const auto* fido_eligible_card_ids = response.FindKeyOfType( |
| "fido_eligible_credit_card_id", base::Value::Type::LIST); |
| if (fido_eligible_card_ids) { |
| for (const base::Value& result : fido_eligible_card_ids->GetList()) { |
| unmask_details_.fido_eligible_card_ids.insert(result.GetString()); |
| } |
| } |
| } |
| |
| bool IsResponseComplete() override { |
| return unmask_details_.unmask_auth_method != |
| AutofillClient::UnmaskAuthMethod::UNKNOWN; |
| } |
| |
| void RespondToDelegate(AutofillClient::PaymentsRpcResult result) override { |
| std::move(callback_).Run(result, unmask_details_); |
| } |
| |
| private: |
| GetUnmaskDetailsCallback callback_; |
| std::string app_locale_; |
| const bool full_sync_enabled_; |
| |
| // Suggested authentication method and other information to facilitate card |
| // unmasking. |
| AutofillClient::UnmaskDetails unmask_details_; |
| DISALLOW_COPY_AND_ASSIGN(GetUnmaskDetailsRequest); |
| }; |
| |
| class UnmaskCardRequest : public PaymentsRequest { |
| public: |
| UnmaskCardRequest( |
| const PaymentsClient::UnmaskRequestDetails& request_details, |
| const bool full_sync_enabled, |
| base::OnceCallback<void(AutofillClient::PaymentsRpcResult, |
| PaymentsClient::UnmaskResponseDetails&)> callback) |
| : request_details_(request_details), |
| full_sync_enabled_(full_sync_enabled), |
| callback_(std::move(callback)) { |
| DCHECK( |
| CreditCard::MASKED_SERVER_CARD == request_details.card.record_type() || |
| CreditCard::FULL_SERVER_CARD == request_details.card.record_type()); |
| } |
| ~UnmaskCardRequest() override {} |
| |
| std::string GetRequestUrlPath() override { return kUnmaskCardRequestPath; } |
| |
| std::string GetRequestContentType() override { |
| return "application/x-www-form-urlencoded"; |
| } |
| |
| std::string GetRequestContent() override { |
| base::Value request_dict(base::Value::Type::DICTIONARY); |
| request_dict.SetKey("encrypted_cvc", base::Value("__param:s7e_13_cvc")); |
| request_dict.SetKey("credit_card_id", |
| base::Value(request_details_.card.server_id())); |
| request_dict.SetKey("risk_data_encoded", |
| BuildRiskDictionary(request_details_.risk_data)); |
| base::Value context(base::Value::Type::DICTIONARY); |
| context.SetKey("billable_service", |
| base::Value(kUnmaskCardBillableServiceNumber)); |
| if (request_details_.billing_customer_number != 0) { |
| context.SetKey("customer_context", |
| BuildCustomerContextDictionary( |
| request_details_.billing_customer_number)); |
| } |
| request_dict.SetKey("context", std::move(context)); |
| |
| if (ShouldUseActiveSignedInAccount()) { |
| base::Value chrome_user_context(base::Value::Type::DICTIONARY); |
| chrome_user_context.SetKey("full_sync_enabled", |
| base::Value(full_sync_enabled_)); |
| request_dict.SetKey("chrome_user_context", |
| std::move(chrome_user_context)); |
| } |
| |
| int value = 0; |
| if (base::StringToInt(request_details_.user_response.exp_month, &value)) |
| request_dict.SetKey("expiration_month", base::Value(value)); |
| if (base::StringToInt(request_details_.user_response.exp_year, &value)) |
| request_dict.SetKey("expiration_year", base::Value(value)); |
| |
| request_dict.SetKey( |
| "opt_in_fido_auth", |
| base::Value(request_details_.user_response.enable_fido_auth)); |
| |
| if (request_details_.fido_assertion_info.is_dict()) { |
| request_dict.SetKey("fido_assertion_info", |
| std::move(request_details_.fido_assertion_info)); |
| } |
| |
| std::string json_request; |
| base::JSONWriter::Write(request_dict, &json_request); |
| std::string request_content = base::StringPrintf( |
| kUnmaskCardRequestFormat, |
| net::EscapeUrlEncodedData(json_request, true).c_str(), |
| net::EscapeUrlEncodedData( |
| base::UTF16ToASCII(request_details_.user_response.cvc), true) |
| .c_str()); |
| |
| // Payments is reporting receiving blank or non-standard-length CVCs. |
| // Log CVC length being sent to gauge how often this is happening. |
| if (request_details_.reason == AutofillClient::UNMASK_FOR_AUTOFILL) { |
| base::UmaHistogramCounts1000("Autofill.CardUnmask.CvcLength.ForAutofill", |
| request_details_.user_response.cvc.length()); |
| } else if (request_details_.reason == |
| AutofillClient::UNMASK_FOR_PAYMENT_REQUEST) { |
| base::UmaHistogramCounts1000( |
| "Autofill.CardUnmask.CvcLength.ForPaymentRequest", |
| request_details_.user_response.cvc.length()); |
| } |
| |
| VLOG(3) << "getrealpan request body: " << request_content; |
| return request_content; |
| } |
| |
| void ParseResponse(const base::Value& response) override { |
| const auto* pan = response.FindStringKey("pan"); |
| response_details_.real_pan = pan ? *pan : std::string(); |
| |
| const auto* creation_options = response.FindKeyOfType( |
| "fido_creation_options", base::Value::Type::DICTIONARY); |
| if (creation_options) |
| response_details_.fido_creation_options = creation_options->Clone(); |
| } |
| |
| bool IsResponseComplete() override { |
| return !response_details_.real_pan.empty(); |
| } |
| |
| void RespondToDelegate(AutofillClient::PaymentsRpcResult result) override { |
| std::move(callback_).Run(result, response_details_); |
| } |
| |
| private: |
| PaymentsClient::UnmaskRequestDetails request_details_; |
| const bool full_sync_enabled_; |
| base::OnceCallback<void(AutofillClient::PaymentsRpcResult, |
| PaymentsClient::UnmaskResponseDetails&)> |
| callback_; |
| PaymentsClient::UnmaskResponseDetails response_details_; |
| |
| DISALLOW_COPY_AND_ASSIGN(UnmaskCardRequest); |
| }; |
| |
| class OptChangeRequest : public PaymentsRequest { |
| public: |
| OptChangeRequest( |
| const PaymentsClient::OptChangeRequestDetails& request_details, |
| OptChangeCallback callback, |
| const bool full_sync_enabled) |
| : request_details_(request_details), |
| callback_(std::move(callback)), |
| full_sync_enabled_(full_sync_enabled) {} |
| ~OptChangeRequest() override {} |
| |
| std::string GetRequestUrlPath() override { return kOptChangeRequestPath; } |
| |
| std::string GetRequestContentType() override { return "application/json"; } |
| |
| std::string GetRequestContent() override { |
| base::Value request_dict(base::Value::Type::DICTIONARY); |
| base::Value context(base::Value::Type::DICTIONARY); |
| context.SetKey("language_code", base::Value(request_details_.app_locale)); |
| context.SetKey("billable_service", |
| base::Value(kUnmaskCardBillableServiceNumber)); |
| request_dict.SetKey("context", std::move(context)); |
| |
| if (ShouldUseActiveSignedInAccount()) { |
| base::Value chrome_user_context(base::Value::Type::DICTIONARY); |
| chrome_user_context.SetKey("full_sync_enabled", |
| base::Value(full_sync_enabled_)); |
| request_dict.SetKey("chrome_user_context", |
| std::move(chrome_user_context)); |
| } |
| |
| request_dict.SetKey("opt_in", base::Value(request_details_.opt_in)); |
| |
| if (request_details_.fido_authenticator_response.is_dict()) { |
| request_dict.SetKey( |
| "fido_authenticator_response", |
| std::move(request_details_.fido_authenticator_response)); |
| } |
| |
| std::string request_content; |
| base::JSONWriter::Write(request_dict, &request_content); |
| VLOG(3) << "autofillauthoptchange request body: " << request_content; |
| return request_content; |
| } |
| |
| void ParseResponse(const base::Value& response) override { |
| const auto* user_is_opted_in = |
| response.FindKeyOfType("user_is_opted_in", base::Value::Type::BOOLEAN); |
| if (user_is_opted_in) |
| user_is_opted_in_ = user_is_opted_in->GetBool(); |
| |
| const auto* fido_creation_options = response.FindKeyOfType( |
| "fido_creation_options", base::Value::Type::DICTIONARY); |
| if (fido_creation_options) |
| fido_creation_options_ = fido_creation_options->Clone(); |
| } |
| |
| bool IsResponseComplete() override { return user_is_opted_in_.has_value(); } |
| |
| void RespondToDelegate(AutofillClient::PaymentsRpcResult result) override { |
| std::move(callback_).Run( |
| result, user_is_opted_in_.value_or(!request_details_.opt_in), |
| std::move(fido_creation_options_)); |
| } |
| |
| private: |
| PaymentsClient::OptChangeRequestDetails request_details_; |
| OptChangeCallback callback_; |
| const bool full_sync_enabled_; |
| base::Optional<bool> user_is_opted_in_; |
| base::Value fido_creation_options_; |
| |
| DISALLOW_COPY_AND_ASSIGN(OptChangeRequest); |
| }; |
| |
| class GetUploadDetailsRequest : public PaymentsRequest { |
| public: |
| GetUploadDetailsRequest( |
| const std::vector<AutofillProfile>& addresses, |
| const int detected_values, |
| const std::vector<const char*>& active_experiments, |
| const bool full_sync_enabled, |
| const std::string& app_locale, |
| base::OnceCallback<void(AutofillClient::PaymentsRpcResult, |
| const base::string16&, |
| std::unique_ptr<base::Value>, |
| std::vector<std::pair<int, int>>)> callback, |
| const int billable_service_number, |
| PaymentsClient::UploadCardSource upload_card_source) |
| : addresses_(addresses), |
| detected_values_(detected_values), |
| active_experiments_(active_experiments), |
| full_sync_enabled_(full_sync_enabled), |
| app_locale_(app_locale), |
| callback_(std::move(callback)), |
| billable_service_number_(billable_service_number), |
| upload_card_source_(upload_card_source) {} |
| ~GetUploadDetailsRequest() override {} |
| |
| std::string GetRequestUrlPath() override { |
| return kGetUploadDetailsRequestPath; |
| } |
| |
| std::string GetRequestContentType() override { return "application/json"; } |
| |
| std::string GetRequestContent() override { |
| base::Value request_dict(base::Value::Type::DICTIONARY); |
| base::Value context(base::Value::Type::DICTIONARY); |
| context.SetKey("language_code", base::Value(app_locale_)); |
| context.SetKey("billable_service", base::Value(billable_service_number_)); |
| request_dict.SetKey("context", std::move(context)); |
| |
| if (ShouldUseActiveSignedInAccount()) { |
| base::Value chrome_user_context(base::Value::Type::DICTIONARY); |
| chrome_user_context.SetKey("full_sync_enabled", |
| base::Value(full_sync_enabled_)); |
| request_dict.SetKey("chrome_user_context", |
| std::move(chrome_user_context)); |
| } |
| |
| base::Value addresses(base::Value::Type::LIST); |
| for (const AutofillProfile& profile : addresses_) { |
| // These addresses are used by Payments to (1) accurately determine the |
| // user's country in order to show the correct legal documents and (2) to |
| // verify that the addresses are valid for their purposes so that we don't |
| // offer save in a case where it would definitely fail (e.g. P.O. boxes if |
| // min address is not possible). The final parameter directs |
| // BuildAddressDictionary to omit names and phone numbers, which aren't |
| // useful for these purposes. |
| addresses.Append(BuildAddressDictionary(profile, app_locale_, false)); |
| } |
| request_dict.SetKey("address", std::move(addresses)); |
| |
| // It's possible we may not have found name/address/CVC in the checkout |
| // flow. The detected_values_ bitmask tells Payments what *was* found, and |
| // Payments will decide if the provided data is enough to offer upload save. |
| request_dict.SetKey("detected_values", base::Value(detected_values_)); |
| |
| SetActiveExperiments(active_experiments_, request_dict); |
| |
| switch (upload_card_source_) { |
| case PaymentsClient::UploadCardSource::UNKNOWN_UPLOAD_CARD_SOURCE: |
| request_dict.SetKey("upload_card_source", |
| base::Value("UNKNOWN_UPLOAD_CARD_SOURCE")); |
| break; |
| case PaymentsClient::UploadCardSource::UPSTREAM_CHECKOUT_FLOW: |
| request_dict.SetKey("upload_card_source", |
| base::Value("UPSTREAM_CHECKOUT_FLOW")); |
| break; |
| case PaymentsClient::UploadCardSource::UPSTREAM_SETTINGS_PAGE: |
| request_dict.SetKey("upload_card_source", |
| base::Value("UPSTREAM_SETTINGS_PAGE")); |
| break; |
| case PaymentsClient::UploadCardSource::UPSTREAM_CARD_OCR: |
| request_dict.SetKey("upload_card_source", |
| base::Value("UPSTREAM_CARD_OCR")); |
| break; |
| case PaymentsClient::UploadCardSource::LOCAL_CARD_MIGRATION_CHECKOUT_FLOW: |
| request_dict.SetKey("upload_card_source", |
| base::Value("LOCAL_CARD_MIGRATION_CHECKOUT_FLOW")); |
| break; |
| case PaymentsClient::UploadCardSource::LOCAL_CARD_MIGRATION_SETTINGS_PAGE: |
| request_dict.SetKey("upload_card_source", |
| base::Value("LOCAL_CARD_MIGRATION_SETTINGS_PAGE")); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| |
| std::string request_content; |
| base::JSONWriter::Write(request_dict, &request_content); |
| VLOG(3) << "getdetailsforsavecard request body: " << request_content; |
| return request_content; |
| } |
| |
| void ParseResponse(const base::Value& response) override { |
| const auto* context_token = response.FindStringKey("context_token"); |
| context_token_ = |
| context_token ? base::UTF8ToUTF16(*context_token) : base::string16(); |
| |
| const base::Value* dictionary_value = |
| response.FindKeyOfType("legal_message", base::Value::Type::DICTIONARY); |
| if (dictionary_value) |
| legal_message_ = std::make_unique<base::Value>(dictionary_value->Clone()); |
| |
| const auto* supported_card_bin_ranges_string = |
| response.FindStringKey("supported_card_bin_ranges_string"); |
| supported_card_bin_ranges_ = ParseSupportedCardBinRangesString( |
| supported_card_bin_ranges_string ? *supported_card_bin_ranges_string |
| : base::EmptyString()); |
| } |
| |
| bool IsResponseComplete() override { |
| return !context_token_.empty() && legal_message_; |
| } |
| |
| void RespondToDelegate(AutofillClient::PaymentsRpcResult result) override { |
| std::move(callback_).Run(result, context_token_, std::move(legal_message_), |
| supported_card_bin_ranges_); |
| } |
| |
| private: |
| // Helper for ParseResponse(). Input format should be :"1234,30000-55555,765", |
| // where ranges are separated by commas and items separated with a dash means |
| // the start and ends of the range. Items without a dash have the same start |
| // and end (ex. 1234-1234) |
| std::vector<std::pair<int, int>> ParseSupportedCardBinRangesString( |
| const std::string& supported_card_bin_ranges_string) { |
| std::vector<std::pair<int, int>> supported_card_bin_ranges; |
| std::vector<std::string> range_strings = |
| base::SplitString(supported_card_bin_ranges_string, ",", |
| base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| |
| for (std::string& range_string : range_strings) { |
| std::vector<std::string> range = base::SplitString( |
| range_string, "-", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| DCHECK(range.size() <= 2); |
| int start; |
| base::StringToInt(range[0], &start); |
| if (range.size() == 1) { |
| supported_card_bin_ranges.push_back(std::make_pair(start, start)); |
| } else { |
| int end; |
| base::StringToInt(range[1], &end); |
| DCHECK_LE(start, end); |
| supported_card_bin_ranges.push_back(std::make_pair(start, end)); |
| } |
| } |
| return supported_card_bin_ranges; |
| } |
| |
| const std::vector<AutofillProfile> addresses_; |
| const int detected_values_; |
| const std::vector<const char*> active_experiments_; |
| const bool full_sync_enabled_; |
| std::string app_locale_; |
| base::OnceCallback<void(AutofillClient::PaymentsRpcResult, |
| const base::string16&, |
| std::unique_ptr<base::Value>, |
| std::vector<std::pair<int, int>>)> |
| callback_; |
| base::string16 context_token_; |
| std::unique_ptr<base::Value> legal_message_; |
| std::vector<std::pair<int, int>> supported_card_bin_ranges_; |
| const int billable_service_number_; |
| PaymentsClient::UploadCardSource upload_card_source_; |
| |
| DISALLOW_COPY_AND_ASSIGN(GetUploadDetailsRequest); |
| }; |
| |
| class UploadCardRequest : public PaymentsRequest { |
| public: |
| UploadCardRequest(const PaymentsClient::UploadRequestDetails& request_details, |
| const bool full_sync_enabled, |
| base::OnceCallback<void(AutofillClient::PaymentsRpcResult, |
| const std::string&)> callback) |
| : request_details_(request_details), |
| full_sync_enabled_(full_sync_enabled), |
| callback_(std::move(callback)) {} |
| ~UploadCardRequest() override {} |
| |
| std::string GetRequestUrlPath() override { return kUploadCardRequestPath; } |
| |
| std::string GetRequestContentType() override { |
| return "application/x-www-form-urlencoded"; |
| } |
| |
| std::string GetRequestContent() override { |
| base::Value request_dict(base::Value::Type::DICTIONARY); |
| request_dict.SetKey("encrypted_pan", base::Value("__param:s7e_1_pan")); |
| if (!request_details_.cvc.empty()) |
| request_dict.SetKey("encrypted_cvc", base::Value("__param:s7e_13_cvc")); |
| request_dict.SetKey("risk_data_encoded", |
| BuildRiskDictionary(request_details_.risk_data)); |
| |
| const std::string& app_locale = request_details_.app_locale; |
| base::Value context(base::Value::Type::DICTIONARY); |
| context.SetKey("language_code", base::Value(app_locale)); |
| context.SetKey("billable_service", |
| base::Value(kUploadCardBillableServiceNumber)); |
| if (request_details_.billing_customer_number != 0) { |
| context.SetKey("customer_context", |
| BuildCustomerContextDictionary( |
| request_details_.billing_customer_number)); |
| } |
| request_dict.SetKey("context", std::move(context)); |
| |
| if (ShouldUseActiveSignedInAccount()) { |
| base::Value chrome_user_context(base::Value::Type::DICTIONARY); |
| chrome_user_context.SetKey("full_sync_enabled", |
| base::Value(full_sync_enabled_)); |
| request_dict.SetKey("chrome_user_context", |
| std::move(chrome_user_context)); |
| } |
| |
| SetStringIfNotEmpty(request_details_.card, CREDIT_CARD_NAME_FULL, |
| app_locale, "cardholder_name", request_dict); |
| |
| base::Value addresses(base::Value::Type::LIST); |
| for (const AutofillProfile& profile : request_details_.profiles) { |
| addresses.Append(BuildAddressDictionary(profile, app_locale, true)); |
| } |
| request_dict.SetKey("address", std::move(addresses)); |
| |
| request_dict.SetKey("context_token", |
| base::Value(request_details_.context_token)); |
| |
| int value = 0; |
| const base::string16 exp_month = request_details_.card.GetInfo( |
| AutofillType(CREDIT_CARD_EXP_MONTH), app_locale); |
| const base::string16 exp_year = request_details_.card.GetInfo( |
| AutofillType(CREDIT_CARD_EXP_4_DIGIT_YEAR), app_locale); |
| if (base::StringToInt(exp_month, &value)) |
| request_dict.SetKey("expiration_month", base::Value(value)); |
| if (base::StringToInt(exp_year, &value)) |
| request_dict.SetKey("expiration_year", base::Value(value)); |
| |
| SetActiveExperiments(request_details_.active_experiments, request_dict); |
| |
| const base::string16 pan = request_details_.card.GetInfo( |
| AutofillType(CREDIT_CARD_NUMBER), app_locale); |
| std::string json_request; |
| base::JSONWriter::Write(request_dict, &json_request); |
| std::string request_content; |
| if (request_details_.cvc.empty()) { |
| request_content = base::StringPrintf( |
| kUploadCardRequestFormatWithoutCvc, |
| net::EscapeUrlEncodedData(json_request, true).c_str(), |
| net::EscapeUrlEncodedData(base::UTF16ToASCII(pan), true).c_str()); |
| } else { |
| request_content = base::StringPrintf( |
| kUploadCardRequestFormat, |
| net::EscapeUrlEncodedData(json_request, true).c_str(), |
| net::EscapeUrlEncodedData(base::UTF16ToASCII(pan), true).c_str(), |
| net::EscapeUrlEncodedData(base::UTF16ToASCII(request_details_.cvc), |
| true) |
| .c_str()); |
| } |
| VLOG(3) << "savecard request body: " << request_content; |
| return request_content; |
| } |
| |
| void ParseResponse(const base::Value& response) override { |
| const std::string* credit_card_id = |
| response.FindStringKey("credit_card_id"); |
| server_id_ = credit_card_id ? *credit_card_id : std::string(); |
| } |
| |
| bool IsResponseComplete() override { return true; } |
| |
| void RespondToDelegate(AutofillClient::PaymentsRpcResult result) override { |
| std::move(callback_).Run(result, server_id_); |
| } |
| |
| private: |
| const PaymentsClient::UploadRequestDetails request_details_; |
| const bool full_sync_enabled_; |
| base::OnceCallback<void(AutofillClient::PaymentsRpcResult, |
| const std::string&)> |
| callback_; |
| std::string server_id_; |
| |
| DISALLOW_COPY_AND_ASSIGN(UploadCardRequest); |
| }; |
| |
| class MigrateCardsRequest : public PaymentsRequest { |
| public: |
| MigrateCardsRequest( |
| const PaymentsClient::MigrationRequestDetails& request_details, |
| const std::vector<MigratableCreditCard>& migratable_credit_cards, |
| const bool full_sync_enabled, |
| MigrateCardsCallback callback) |
| : request_details_(request_details), |
| migratable_credit_cards_(migratable_credit_cards), |
| full_sync_enabled_(full_sync_enabled), |
| callback_(std::move(callback)) {} |
| ~MigrateCardsRequest() override {} |
| |
| std::string GetRequestUrlPath() override { return kMigrateCardsRequestPath; } |
| |
| std::string GetRequestContentType() override { |
| return "application/x-www-form-urlencoded"; |
| } |
| |
| std::string GetRequestContent() override { |
| base::Value request_dict(base::Value::Type::DICTIONARY); |
| |
| request_dict.SetKey("risk_data_encoded", |
| BuildRiskDictionary(request_details_.risk_data)); |
| |
| const std::string& app_locale = request_details_.app_locale; |
| base::Value context(base::Value::Type::DICTIONARY); |
| context.SetKey("language_code", base::Value(app_locale)); |
| context.SetKey("billable_service", |
| base::Value(kMigrateCardsBillableServiceNumber)); |
| if (request_details_.billing_customer_number != 0) { |
| context.SetKey("customer_context", |
| BuildCustomerContextDictionary( |
| request_details_.billing_customer_number)); |
| } |
| request_dict.SetKey("context", std::move(context)); |
| |
| if (ShouldUseActiveSignedInAccount()) { |
| base::Value chrome_user_context(base::Value::Type::DICTIONARY); |
| chrome_user_context.SetKey("full_sync_enabled", |
| base::Value(full_sync_enabled_)); |
| request_dict.SetKey("chrome_user_context", |
| std::move(chrome_user_context)); |
| } |
| |
| request_dict.SetKey("context_token", |
| base::Value(request_details_.context_token)); |
| |
| std::string all_pans_data = std::string(); |
| base::Value migrate_cards(base::Value::Type::LIST); |
| for (size_t index = 0; index < migratable_credit_cards_.size(); ++index) { |
| std::string pan_field_name = GetPanFieldName(index); |
| // Generate credit card dictionary. |
| migrate_cards.Append(BuildCreditCardDictionary( |
| migratable_credit_cards_[index].credit_card(), app_locale, |
| pan_field_name)); |
| // Append pan data to the |all_pans_data|. |
| all_pans_data += |
| GetAppendPan(migratable_credit_cards_[index].credit_card(), |
| app_locale, pan_field_name); |
| } |
| request_dict.SetKey("local_card", std::move(migrate_cards)); |
| |
| std::string json_request; |
| base::JSONWriter::Write(request_dict, &json_request); |
| std::string request_content = base::StringPrintf( |
| kMigrateCardsRequestFormat, |
| net::EscapeUrlEncodedData(json_request, true).c_str()); |
| request_content += all_pans_data; |
| return request_content; |
| } |
| |
| void ParseResponse(const base::Value& response) override { |
| const auto* found_list = |
| response.FindKeyOfType("save_result", base::Value::Type::LIST); |
| if (!found_list) |
| return; |
| |
| save_result_ = |
| std::make_unique<std::unordered_map<std::string, std::string>>(); |
| for (const base::Value& result : found_list->GetList()) { |
| if (result.is_dict()) { |
| const std::string* unique_id = result.FindStringKey("unique_id"); |
| const std::string* status = result.FindStringKey("status"); |
| save_result_->insert( |
| std::make_pair(unique_id ? *unique_id : std::string(), |
| status ? *status : std::string())); |
| } |
| } |
| |
| const std::string* display_text = |
| response.FindStringKey("value_prop_display_text"); |
| display_text_ = display_text ? *display_text : std::string(); |
| } |
| |
| bool IsResponseComplete() override { |
| return !display_text_.empty() && save_result_; |
| } |
| |
| void RespondToDelegate(AutofillClient::PaymentsRpcResult result) override { |
| std::move(callback_).Run(result, std::move(save_result_), display_text_); |
| } |
| |
| private: |
| // Return the pan field name for the encrypted pan based on the |index|. |
| std::string GetPanFieldName(const size_t& index) { |
| return "s7e_1_pan" + std::to_string(index); |
| } |
| |
| // Return the formatted pan to append to the end of the request. |
| std::string GetAppendPan(const CreditCard& credit_card, |
| const std::string& app_locale, |
| const std::string& pan_field_name) { |
| const base::string16 pan = |
| credit_card.GetInfo(AutofillType(CREDIT_CARD_NUMBER), app_locale); |
| std::string pan_str = |
| net::EscapeUrlEncodedData(base::UTF16ToASCII(pan), true).c_str(); |
| std::string append_pan = "&" + pan_field_name + "=" + pan_str; |
| return append_pan; |
| } |
| |
| const PaymentsClient::MigrationRequestDetails request_details_; |
| const std::vector<MigratableCreditCard>& migratable_credit_cards_; |
| const bool full_sync_enabled_; |
| MigrateCardsCallback callback_; |
| std::unique_ptr<std::unordered_map<std::string, std::string>> save_result_; |
| std::string display_text_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MigrateCardsRequest); |
| }; |
| |
| } // namespace |
| |
| const char PaymentsClient::kRecipientName[] = "recipient_name"; |
| const char PaymentsClient::kPhoneNumber[] = "phone_number"; |
| |
| PaymentsClient::UnmaskRequestDetails::UnmaskRequestDetails() {} |
| PaymentsClient::UnmaskRequestDetails::UnmaskRequestDetails( |
| const UnmaskRequestDetails& other) { |
| billing_customer_number = other.billing_customer_number; |
| reason = other.reason; |
| card = other.card; |
| risk_data = other.risk_data; |
| user_response = other.user_response; |
| fido_assertion_info = other.fido_assertion_info.Clone(); |
| } |
| PaymentsClient::UnmaskRequestDetails::~UnmaskRequestDetails() {} |
| |
| PaymentsClient::UnmaskResponseDetails::UnmaskResponseDetails() {} |
| PaymentsClient::UnmaskResponseDetails::UnmaskResponseDetails( |
| const UnmaskResponseDetails& other) { |
| *this = other; |
| } |
| PaymentsClient::UnmaskResponseDetails::~UnmaskResponseDetails() {} |
| PaymentsClient::UnmaskResponseDetails& PaymentsClient::UnmaskResponseDetails:: |
| operator=(const PaymentsClient::UnmaskResponseDetails& other) { |
| real_pan = other.real_pan; |
| if (other.fido_creation_options.has_value()) { |
| fido_creation_options = other.fido_creation_options->Clone(); |
| } else { |
| fido_creation_options.reset(); |
| } |
| return *this; |
| } |
| |
| PaymentsClient::OptChangeRequestDetails::OptChangeRequestDetails() {} |
| PaymentsClient::OptChangeRequestDetails::OptChangeRequestDetails( |
| const OptChangeRequestDetails& other) { |
| app_locale = other.app_locale; |
| opt_in = other.opt_in; |
| fido_authenticator_response = other.fido_authenticator_response.Clone(); |
| } |
| PaymentsClient::OptChangeRequestDetails::~OptChangeRequestDetails() {} |
| |
| PaymentsClient::UploadRequestDetails::UploadRequestDetails() {} |
| PaymentsClient::UploadRequestDetails::UploadRequestDetails( |
| const UploadRequestDetails& other) = default; |
| PaymentsClient::UploadRequestDetails::~UploadRequestDetails() {} |
| |
| PaymentsClient::MigrationRequestDetails::MigrationRequestDetails() {} |
| PaymentsClient::MigrationRequestDetails::MigrationRequestDetails( |
| const MigrationRequestDetails& other) = default; |
| PaymentsClient::MigrationRequestDetails::~MigrationRequestDetails() {} |
| |
| PaymentsClient::PaymentsClient( |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, |
| signin::IdentityManager* identity_manager, |
| AccountInfoGetter* account_info_getter, |
| bool is_off_the_record) |
| : url_loader_factory_(url_loader_factory), |
| identity_manager_(identity_manager), |
| account_info_getter_(account_info_getter), |
| is_off_the_record_(is_off_the_record), |
| has_retried_authorization_(false) {} |
| |
| PaymentsClient::~PaymentsClient() {} |
| |
| void PaymentsClient::Prepare() { |
| if (access_token_.empty()) |
| StartTokenFetch(false); |
| } |
| |
| void PaymentsClient::GetUnmaskDetails(GetUnmaskDetailsCallback callback, |
| const std::string& app_locale) { |
| IssueRequest(std::make_unique<GetUnmaskDetailsRequest>( |
| std::move(callback), app_locale, |
| account_info_getter_->IsSyncFeatureEnabled()), |
| /*authenticate=*/true); |
| } |
| |
| void PaymentsClient::UnmaskCard( |
| const PaymentsClient::UnmaskRequestDetails& request_details, |
| base::OnceCallback<void(AutofillClient::PaymentsRpcResult, |
| PaymentsClient::UnmaskResponseDetails&)> callback) { |
| IssueRequest( |
| std::make_unique<UnmaskCardRequest>( |
| request_details, account_info_getter_->IsSyncFeatureEnabled(), |
| std::move(callback)), |
| /*authenticate=*/true); |
| } |
| |
| void PaymentsClient::OptChange(const OptChangeRequestDetails request_details, |
| OptChangeCallback callback) { |
| IssueRequest(std::make_unique<OptChangeRequest>( |
| request_details, std::move(callback), |
| account_info_getter_->IsSyncFeatureEnabled()), |
| /*authenticate=*/true); |
| } |
| |
| void PaymentsClient::GetUploadDetails( |
| const std::vector<AutofillProfile>& addresses, |
| const int detected_values, |
| const std::vector<const char*>& active_experiments, |
| const std::string& app_locale, |
| base::OnceCallback<void(AutofillClient::PaymentsRpcResult, |
| const base::string16&, |
| std::unique_ptr<base::Value>, |
| std::vector<std::pair<int, int>>)> callback, |
| const int billable_service_number, |
| UploadCardSource upload_card_source) { |
| IssueRequest( |
| std::make_unique<GetUploadDetailsRequest>( |
| addresses, detected_values, active_experiments, |
| account_info_getter_->IsSyncFeatureEnabled(), app_locale, |
| std::move(callback), billable_service_number, upload_card_source), |
| /*authenticate=*/false); |
| } |
| |
| void PaymentsClient::UploadCard( |
| const PaymentsClient::UploadRequestDetails& request_details, |
| base::OnceCallback<void(AutofillClient::PaymentsRpcResult, |
| const std::string&)> callback) { |
| IssueRequest( |
| std::make_unique<UploadCardRequest>( |
| request_details, account_info_getter_->IsSyncFeatureEnabled(), |
| std::move(callback)), |
| /*authenticate=*/true); |
| } |
| |
| void PaymentsClient::MigrateCards( |
| const MigrationRequestDetails& request_details, |
| const std::vector<MigratableCreditCard>& migratable_credit_cards, |
| MigrateCardsCallback callback) { |
| IssueRequest( |
| std::make_unique<MigrateCardsRequest>( |
| request_details, migratable_credit_cards, |
| account_info_getter_->IsSyncFeatureEnabled(), std::move(callback)), |
| /*authenticate=*/true); |
| } |
| |
| void PaymentsClient::CancelRequest() { |
| request_.reset(); |
| resource_request_.reset(); |
| simple_url_loader_.reset(); |
| token_fetcher_.reset(); |
| access_token_.clear(); |
| has_retried_authorization_ = false; |
| } |
| |
| void PaymentsClient::set_url_loader_factory_for_testing( |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) { |
| url_loader_factory_ = std::move(url_loader_factory); |
| } |
| |
| void PaymentsClient::IssueRequest(std::unique_ptr<PaymentsRequest> request, |
| bool authenticate) { |
| request_ = std::move(request); |
| has_retried_authorization_ = false; |
| |
| InitializeResourceRequest(); |
| |
| if (!authenticate) { |
| StartRequest(); |
| } else if (access_token_.empty()) { |
| StartTokenFetch(false); |
| } else { |
| SetOAuth2TokenAndStartRequest(); |
| } |
| } |
| |
| void PaymentsClient::InitializeResourceRequest() { |
| resource_request_ = std::make_unique<network::ResourceRequest>(); |
| resource_request_->url = GetRequestUrl(request_->GetRequestUrlPath()); |
| resource_request_->load_flags = net::LOAD_DISABLE_CACHE; |
| resource_request_->credentials_mode = network::mojom::CredentialsMode::kOmit; |
| resource_request_->method = "POST"; |
| |
| // Add Chrome experiment state to the request headers. |
| net::HttpRequestHeaders headers; |
| // User is always signed-in to be able to upload card to Google Payments. |
| variations::AppendVariationsHeader( |
| resource_request_->url, |
| is_off_the_record_ ? variations::InIncognito::kYes |
| : variations::InIncognito::kNo, |
| variations::SignedIn::kYes, resource_request_.get()); |
| } |
| |
| void PaymentsClient::OnSimpleLoaderComplete( |
| std::unique_ptr<std::string> response_body) { |
| int response_code = -1; |
| if (simple_url_loader_->ResponseInfo() && |
| simple_url_loader_->ResponseInfo()->headers) { |
| response_code = |
| simple_url_loader_->ResponseInfo()->headers->response_code(); |
| } |
| std::string data; |
| if (response_body) |
| data = std::move(*response_body); |
| OnSimpleLoaderCompleteInternal(response_code, data); |
| } |
| |
| void PaymentsClient::OnSimpleLoaderCompleteInternal(int response_code, |
| const std::string& data) { |
| VLOG(2) << "Got data: " << data; |
| |
| AutofillClient::PaymentsRpcResult result = AutofillClient::SUCCESS; |
| |
| switch (response_code) { |
| // Valid response. |
| case net::HTTP_OK: { |
| std::string error_code; |
| base::Optional<base::Value> message_value = base::JSONReader::Read(data); |
| if (message_value && message_value->is_dict()) { |
| const auto* found = message_value->FindPathOfType( |
| {"error", "code"}, base::Value::Type::STRING); |
| if (found) |
| error_code = found->GetString(); |
| request_->ParseResponse(*message_value); |
| } |
| |
| if (base::LowerCaseEqualsASCII(error_code, "internal")) |
| result = AutofillClient::TRY_AGAIN_FAILURE; |
| else if (!error_code.empty() || !request_->IsResponseComplete()) |
| result = AutofillClient::PERMANENT_FAILURE; |
| |
| break; |
| } |
| |
| case net::HTTP_UNAUTHORIZED: { |
| if (has_retried_authorization_) { |
| result = AutofillClient::PERMANENT_FAILURE; |
| break; |
| } |
| has_retried_authorization_ = true; |
| |
| InitializeResourceRequest(); |
| StartTokenFetch(true); |
| return; |
| } |
| |
| // TODO(estade): is this actually how network connectivity issues are |
| // reported? |
| case net::HTTP_REQUEST_TIMEOUT: { |
| result = AutofillClient::NETWORK_ERROR; |
| break; |
| } |
| |
| // Handle anything else as a generic (permanent) failure. |
| default: { |
| result = AutofillClient::PERMANENT_FAILURE; |
| break; |
| } |
| } |
| |
| if (result != AutofillClient::SUCCESS) { |
| VLOG(1) << "Payments returned error: " << response_code |
| << " with data: " << data; |
| } |
| |
| request_->RespondToDelegate(result); |
| } |
| |
| void PaymentsClient::AccessTokenFetchFinished( |
| GoogleServiceAuthError error, |
| signin::AccessTokenInfo access_token_info) { |
| DCHECK(token_fetcher_); |
| token_fetcher_.reset(); |
| |
| if (error.state() != GoogleServiceAuthError::NONE) { |
| AccessTokenError(error); |
| return; |
| } |
| |
| access_token_ = access_token_info.token; |
| if (resource_request_) |
| SetOAuth2TokenAndStartRequest(); |
| } |
| |
| void PaymentsClient::AccessTokenError(const GoogleServiceAuthError& error) { |
| VLOG(1) << "Unhandled OAuth2 error: " << error.ToString(); |
| if (simple_url_loader_) |
| simple_url_loader_.reset(); |
| if (request_) |
| request_->RespondToDelegate(AutofillClient::PERMANENT_FAILURE); |
| } |
| |
| void PaymentsClient::StartTokenFetch(bool invalidate_old) { |
| // We're still waiting for the last request to come back. |
| if (!invalidate_old && token_fetcher_) |
| return; |
| |
| DCHECK(account_info_getter_); |
| |
| identity::ScopeSet payments_scopes; |
| payments_scopes.insert(kPaymentsOAuth2Scope); |
| std::string account_id = |
| account_info_getter_->GetAccountInfoForPaymentsServer().account_id; |
| if (invalidate_old) { |
| DCHECK(!access_token_.empty()); |
| identity_manager_->RemoveAccessTokenFromCache(account_id, payments_scopes, |
| access_token_); |
| } |
| access_token_.clear(); |
| token_fetcher_ = identity_manager_->CreateAccessTokenFetcherForAccount( |
| account_id, kTokenFetchId, payments_scopes, |
| base::BindOnce(&PaymentsClient::AccessTokenFetchFinished, |
| base::Unretained(this)), |
| signin::AccessTokenFetcher::Mode::kImmediate); |
| } |
| |
| void PaymentsClient::SetOAuth2TokenAndStartRequest() { |
| DCHECK(resource_request_); |
| resource_request_->headers.SetHeader(net::HttpRequestHeaders::kAuthorization, |
| std::string("Bearer ") + access_token_); |
| StartRequest(); |
| } |
| |
| void PaymentsClient::StartRequest() { |
| DCHECK(resource_request_); |
| net::NetworkTrafficAnnotationTag traffic_annotation = |
| net::DefineNetworkTrafficAnnotation("payments_sync_cards", R"( |
| semantics { |
| sender: "Payments" |
| description: |
| "This service communicates with Google Payments servers to upload " |
| "(save) or receive the user's credit card info." |
| trigger: |
| "Requests are triggered by a user action, such as selecting a " |
| "masked server card from Chromium's credit card autofill dropdown, " |
| "submitting a form which has credit card information, or accepting " |
| "the prompt to save a credit card to Payments servers." |
| data: |
| "In case of save, a protocol buffer containing relevant address " |
| "and credit card information which should be saved in Google " |
| "Payments servers, along with user credentials. In case of load, a " |
| "protocol buffer containing the id of the credit card to unmask, " |
| "an encrypted cvc value, an optional updated card expiration date, " |
| "and user credentials." |
| destination: GOOGLE_OWNED_SERVICE |
| } |
| policy { |
| cookies_allowed: NO |
| setting: |
| "Users can enable or disable this feature in Chromium settings by " |
| "toggling 'Credit cards and addresses using Google Payments', " |
| "under 'Advanced sync settings...'. This feature is enabled by " |
| "default." |
| chrome_policy { |
| AutoFillEnabled { |
| policy_options {mode: MANDATORY} |
| AutoFillEnabled: false |
| } |
| } |
| })"); |
| simple_url_loader_ = network::SimpleURLLoader::Create( |
| std::move(resource_request_), traffic_annotation); |
| simple_url_loader_->AttachStringForUpload(request_->GetRequestContent(), |
| request_->GetRequestContentType()); |
| |
| simple_url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie( |
| url_loader_factory_.get(), |
| base::BindOnce(&PaymentsClient::OnSimpleLoaderComplete, |
| base::Unretained(this))); |
| } |
| |
| } // namespace payments |
| } // namespace autofill |