diff options
Diffstat (limited to 'chromium/components/payments/core')
43 files changed, 3071 insertions, 747 deletions
diff --git a/chromium/components/payments/core/BUILD.gn b/chromium/components/payments/core/BUILD.gn index 1892be27352..eaa3d417ba7 100644 --- a/chromium/components/payments/core/BUILD.gn +++ b/chromium/components/payments/core/BUILD.gn @@ -4,12 +4,17 @@ static_library("core") { sources = [ - "address_normalizer.cc", + "address_normalization_manager.cc", + "address_normalization_manager.h", "address_normalizer.h", + "address_normalizer_impl.cc", + "address_normalizer_impl.h", "autofill_payment_instrument.cc", "autofill_payment_instrument.h", "basic_card_response.cc", "basic_card_response.h", + "can_make_payment_query.cc", + "can_make_payment_query.h", "currency_formatter.cc", "currency_formatter.h", "journey_logger.cc", @@ -21,18 +26,24 @@ static_library("core") { "payment_method_data.cc", "payment_method_data.h", "payment_options_provider.h", + "payment_prefs.cc", + "payment_prefs.h", "payment_request_data_util.cc", "payment_request_data_util.h", "payment_request_delegate.h", - "profile_util.cc", - "profile_util.h", + "payments_profile_comparator.cc", + "payments_profile_comparator.h", "strings_util.cc", "strings_util.h", + "subkey_requester.cc", + "subkey_requester.h", ] deps = [ "//base", "//components/autofill/core/browser", + "//components/keyed_service/core", + "//components/pref_registry", "//components/strings:components_strings_grit", "//components/ukm", "//third_party/libphonenumber", @@ -46,10 +57,27 @@ static_library("core") { ] } +static_library("test_support") { + testonly = true + sources = [ + "test_address_normalizer.cc", + "test_address_normalizer.h", + "test_payment_request_delegate.cc", + "test_payment_request_delegate.h", + ] + + deps = [ + ":core", + "//base", + "//components/autofill/core/browser", + ] +} + source_set("unit_tests") { testonly = true sources = [ - "address_normalizer_unittest.cc", + "address_normalization_manager_unittest.cc", + "address_normalizer_impl_unittest.cc", "autofill_payment_instrument_unittest.cc", "basic_card_response_unittest.cc", "currency_formatter_unittest.cc", @@ -57,20 +85,24 @@ source_set("unit_tests") { "payment_address_unittest.cc", "payment_method_data_unittest.cc", "payment_request_data_util_unittest.cc", - "profile_util_unittest.cc", + "payments_profile_comparator_unittest.cc", + "subkey_requester_unittest.cc", ] deps = [ ":core", + ":test_support", "//base", "//base/test:test_support", "//components/autofill/core/browser", "//components/autofill/core/browser:test_support", "//components/metrics/proto", + "//components/strings:components_strings_grit", "//components/ukm", "//components/ukm:test_support", "//testing/gmock", "//testing/gtest", "//third_party/libaddressinput:test_support", + "//ui/base", ] } diff --git a/chromium/components/payments/core/DEPS b/chromium/components/payments/core/DEPS index 9347542b4fc..20bc8d9c09b 100644 --- a/chromium/components/payments/core/DEPS +++ b/chromium/components/payments/core/DEPS @@ -2,7 +2,9 @@ include_rules = [ "-components/payments/content", "-content", "+components/autofill/core", + "+components/keyed_service/core", "+components/metrics", + "+components/pref_registry", "+components/strings", "+components/ukm", "+third_party/libaddressinput", diff --git a/chromium/components/payments/core/OWNERS b/chromium/components/payments/core/OWNERS new file mode 100644 index 00000000000..396d240ce37 --- /dev/null +++ b/chromium/components/payments/core/OWNERS @@ -0,0 +1,4 @@ +per-file journey_logger*=sebsg@chromium.org +per-file address_normalizer*=sebsg@chromium.org + +# COMPONENT: UI>Browser>Autofill>Payments diff --git a/chromium/components/payments/core/address_normalization_manager.cc b/chromium/components/payments/core/address_normalization_manager.cc new file mode 100644 index 00000000000..21475900670 --- /dev/null +++ b/chromium/components/payments/core/address_normalization_manager.cc @@ -0,0 +1,103 @@ +// Copyright 2017 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/payments/core/address_normalization_manager.h" + +#include "base/memory/ptr_util.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill_data_util.h" +#include "components/autofill/core/browser/field_types.h" + +namespace payments { + +namespace { +constexpr int kAddressNormalizationTimeoutSeconds = 5; +} // namespace + +AddressNormalizationManager::AddressNormalizationManager( + std::unique_ptr<AddressNormalizer> address_normalizer, + const std::string& default_country_code) + : default_country_code_(default_country_code), + address_normalizer_(std::move(address_normalizer)) { + DCHECK(autofill::data_util::IsValidCountryCode(default_country_code)); + DCHECK(address_normalizer_); + + // Start loading rules for the default country code. This happens + // asynchronously, and will speed up normalization later if the rules for the + // address' region have already been loaded. + address_normalizer_->LoadRulesForRegion(default_country_code); +} + +AddressNormalizationManager::~AddressNormalizationManager() {} + +void AddressNormalizationManager::FinalizeWithCompletionCallback( + base::OnceClosure completion_callback) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + completion_callback_ = std::move(completion_callback); + accepting_requests_ = false; + MaybeRunCompletionCallback(); +} + +void AddressNormalizationManager::StartNormalizingAddress( + autofill::AutofillProfile* profile) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + DCHECK(accepting_requests_) << "FinalizeWithCompletionCallback has been " + "called, cannot normalize more addresses"; + + delegates_.push_back(base::MakeUnique<NormalizerDelegate>( + this, address_normalizer_.get(), profile)); +} + +void AddressNormalizationManager::MaybeRunCompletionCallback() { + if (accepting_requests_ || !completion_callback_) + return; + + for (const auto& delegate : delegates_) { + if (!delegate->has_completed()) + return; + } + + // We're no longer accepting requests, and all the delegates have completed. + // Now's the time to run the completion callback. + std::move(completion_callback_).Run(); +} + +AddressNormalizationManager::NormalizerDelegate::NormalizerDelegate( + AddressNormalizationManager* owner, + AddressNormalizer* address_normalizer, + autofill::AutofillProfile* profile) + : owner_(owner), profile_(profile) { + DCHECK(owner_); + DCHECK(profile_); + + std::string country_code = + base::UTF16ToUTF8(profile_->GetRawInfo(autofill::ADDRESS_HOME_COUNTRY)); + if (!autofill::data_util::IsValidCountryCode(country_code)) + country_code = owner_->default_country_code_; + + address_normalizer->StartAddressNormalization( + *profile_, country_code, kAddressNormalizationTimeoutSeconds, this); +} + +void AddressNormalizationManager::NormalizerDelegate::OnAddressNormalized( + const autofill::AutofillProfile& normalized_profile) { + OnCompletion(normalized_profile); +} + +void AddressNormalizationManager::NormalizerDelegate::OnCouldNotNormalize( + const autofill::AutofillProfile& profile) { + // Since the phone number is formatted in either case, this profile should + // be used. + OnCompletion(profile); +} + +void AddressNormalizationManager::NormalizerDelegate::OnCompletion( + const autofill::AutofillProfile& profile) { + DCHECK(!has_completed_); + has_completed_ = true; + *profile_ = profile; + owner_->MaybeRunCompletionCallback(); +} + +} // namespace payments diff --git a/chromium/components/payments/core/address_normalization_manager.h b/chromium/components/payments/core/address_normalization_manager.h new file mode 100644 index 00000000000..a838dbce183 --- /dev/null +++ b/chromium/components/payments/core/address_normalization_manager.h @@ -0,0 +1,107 @@ +// Copyright 2017 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_PAYMENTS_CORE_ADDRESS_NORMALIZATION_MANAGER_H_ +#define COMPONENTS_PAYMENTS_CORE_ADDRESS_NORMALIZATION_MANAGER_H_ + +#include <memory> +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/threading/thread_checker.h" +#include "components/payments/core/address_normalizer.h" + +namespace autofill { +class AutofillProfile; +} // namespace autofill + +namespace payments { + +class AddressNormalizer; + +// Class to handle multiple concurrent address normalization requests. This +// class is not thread-safe. +class AddressNormalizationManager { + public: + // Initializes an AddressNormalizationManager. |default_country_code| will be + // used if the country code in an AutofillProfile to normalize is not valid. + // The AddressNormalizationManager takes ownership of |address_normalizer|. + AddressNormalizationManager( + std::unique_ptr<AddressNormalizer> address_normalizer, + const std::string& default_country_code); + + ~AddressNormalizationManager(); + + // Stops accepting normalization requests. If all the address normalization + // requests have already completed, |completion_callback| will be called + // before this method returns. Otherwise, it will be called as soon as the + // last pending request completes. + void FinalizeWithCompletionCallback(base::OnceClosure completion_callback); + + // Normalizes the address in |profile|. This may or may not happen + // asynchronously. On completion, the address in |profile| will be updated + // with the normalized address. + void StartNormalizingAddress(autofill::AutofillProfile* profile); + + private: + // Implements the payments::AddressNormalizer::Delegate interface, and + // notifies its parent AddressNormalizationManager when normalization has + // completed. + class NormalizerDelegate : public AddressNormalizer::Delegate { + public: + // |owner| is the parent AddressNormalizationManager, |address_normalizer| + // is a pointer to an instance of AddressNormalizer which will handle + // normalization of |profile|. |profile| will be updated when normalization + // is complete. + NormalizerDelegate(AddressNormalizationManager* owner, + AddressNormalizer* address_normalizer, + autofill::AutofillProfile* profile); + + // Returns whether this delegate has completed or not. + bool has_completed() const { return has_completed_; } + + // payments::AddressNormalizer::Delegate: + void OnAddressNormalized( + const autofill::AutofillProfile& normalized_profile) override; + void OnCouldNotNormalize(const autofill::AutofillProfile& profile) override; + + private: + // Helper method that handles when normalization has completed. + void OnCompletion(const autofill::AutofillProfile& profile); + + bool has_completed_ = false; + AddressNormalizationManager* owner_ = nullptr; + autofill::AutofillProfile* profile_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(NormalizerDelegate); + }; + + friend class NormalizerDelegate; + + // Runs the completion callback if all the delegates have completed. + void MaybeRunCompletionCallback(); + + // Whether the AddressNormalizationManager is still accepting requests or not. + bool accepting_requests_ = true; + + // The default country code to use if a profile does not have a valid country. + const std::string default_country_code_; + + // The callback to execute when all addresses have been normalized. + base::OnceClosure completion_callback_; + + // Storage for all the delegates that handle the normalization requests. + std::vector<std::unique_ptr<NormalizerDelegate>> delegates_; + + // The AddressNormalizer to use. Owned by this class. + std::unique_ptr<AddressNormalizer> address_normalizer_; + + THREAD_CHECKER(thread_checker_); + DISALLOW_COPY_AND_ASSIGN(AddressNormalizationManager); +}; + +} // namespace payments + +#endif // COMPONENTS_PAYMENTS_CORE_ADDRESS_NORMALIZATION_MANAGER_H_ diff --git a/chromium/components/payments/core/address_normalization_manager_unittest.cc b/chromium/components/payments/core/address_normalization_manager_unittest.cc new file mode 100644 index 00000000000..c0a60fa2c80 --- /dev/null +++ b/chromium/components/payments/core/address_normalization_manager_unittest.cc @@ -0,0 +1,65 @@ +// Copyright 2017 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/payments/core/address_normalization_manager.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/memory/ptr_util.h" +#include "components/payments/core/test_address_normalizer.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace payments { + +class AddressNormalizationManagerTest : public testing::Test { + protected: + AddressNormalizationManagerTest() {} + + void Initialize(const std::string& country_code) { + std::unique_ptr<TestAddressNormalizer> address_normalizer = + base::MakeUnique<TestAddressNormalizer>(); + address_normalizer_ = address_normalizer.get(); + manager_ = base::MakeUnique<AddressNormalizationManager>( + std::move(address_normalizer), country_code); + } + + void Finalize() { + manager_->FinalizeWithCompletionCallback( + base::BindOnce(&AddressNormalizationManagerTest::CompletionCallback, + base::Unretained(this))); + } + + void CompletionCallback() { completion_callback_called_ = true; } + + std::unique_ptr<AddressNormalizationManager> manager_; + TestAddressNormalizer* address_normalizer_ = nullptr; // Weak. + bool completion_callback_called_ = false; +}; + +TEST_F(AddressNormalizationManagerTest, SynchronousResult) { + Initialize("US"); + + autofill::AutofillProfile profile_to_normalize; + manager_->StartNormalizingAddress(&profile_to_normalize); + + EXPECT_FALSE(completion_callback_called_); + Finalize(); + EXPECT_TRUE(completion_callback_called_); +} + +TEST_F(AddressNormalizationManagerTest, AsynchronousResult) { + Initialize("US"); + address_normalizer_->DelayNormalization(); + + autofill::AutofillProfile profile_to_normalize; + manager_->StartNormalizingAddress(&profile_to_normalize); + + EXPECT_FALSE(completion_callback_called_); + Finalize(); + EXPECT_FALSE(completion_callback_called_); + address_normalizer_->CompleteAddressNormalization(); + EXPECT_TRUE(completion_callback_called_); +} + +} // namespace payments diff --git a/chromium/components/payments/core/address_normalizer.h b/chromium/components/payments/core/address_normalizer.h index f3d2a240fb5..c46371cfcbc 100644 --- a/chromium/components/payments/core/address_normalizer.h +++ b/chromium/components/payments/core/address_normalizer.h @@ -5,25 +5,14 @@ #ifndef COMPONENTS_PAYMENTS_CORE_ADDRESS_NORMALIZER_H_ #define COMPONENTS_PAYMENTS_CORE_ADDRESS_NORMALIZER_H_ -#include <map> -#include <memory> #include <string> -#include <vector> -#include "base/macros.h" #include "third_party/libaddressinput/chromium/chrome_address_validator.h" namespace autofill { class AutofillProfile; } -namespace i18n { -namespace libadderssinput { -class Source; -class Storage; -} -} - namespace payments { // A class used to normalize addresses. @@ -49,16 +38,12 @@ class AddressNormalizer : public autofill::LoadRulesListener { virtual ~Request() {} }; - AddressNormalizer(std::unique_ptr<::i18n::addressinput::Source> source, - std::unique_ptr<::i18n::addressinput::Storage> storage); - ~AddressNormalizer() override; - // Start loading the validation rules for the specified |region_code|. - virtual void LoadRulesForRegion(const std::string& region_code); + virtual void LoadRulesForRegion(const std::string& region_code) = 0; // Returns whether the rules for the specified |region_code| have finished // loading. - bool AreRulesLoadedForRegion(const std::string& region_code); + virtual bool AreRulesLoadedForRegion(const std::string& region_code) = 0; // Starts the normalization of the |profile| based on the |region_code|. The // normalized profile will be returned to the |requester| possibly @@ -68,25 +53,11 @@ class AddressNormalizer : public autofill::LoadRulesListener { // happen synchronously, or not at all if the rules are not already loaded. // Will start loading the rules for the |region_code| if they had not started // loading. - void StartAddressNormalization(const autofill::AutofillProfile& profile, - const std::string& region_code, - int timeout_seconds, - Delegate* requester); - - private: - // Called when the validation rules for the |region_code| have finished - // loading. Implementation of the LoadRulesListener interface. - void OnAddressValidationRulesLoaded(const std::string& region_code, - bool success) override; - - // Map associating a region code to pending normalizations. - std::map<std::string, std::vector<std::unique_ptr<Request>>> - pending_normalization_; - - // The address validator used to normalize addresses. - autofill::AddressValidator address_validator_; - - DISALLOW_COPY_AND_ASSIGN(AddressNormalizer); + virtual void StartAddressNormalization( + const autofill::AutofillProfile& profile, + const std::string& region_code, + int timeout_seconds, + Delegate* requester) = 0; }; } // namespace payments diff --git a/chromium/components/payments/core/address_normalizer.cc b/chromium/components/payments/core/address_normalizer_impl.cc index d432ff11e15..12961bd99c6 100644 --- a/chromium/components/payments/core/address_normalizer.cc +++ b/chromium/components/payments/core/address_normalizer_impl.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "components/payments/core/address_normalizer.h" +#include "components/payments/core/address_normalizer_impl.h" #include <stddef.h> #include <utility> @@ -123,22 +123,22 @@ class AddressNormalizationRequest : public AddressNormalizer::Request { } // namespace -AddressNormalizer::AddressNormalizer(std::unique_ptr<Source> source, - std::unique_ptr<Storage> storage) +AddressNormalizerImpl::AddressNormalizerImpl(std::unique_ptr<Source> source, + std::unique_ptr<Storage> storage) : address_validator_(std::move(source), std::move(storage), this) {} -AddressNormalizer::~AddressNormalizer() {} +AddressNormalizerImpl::~AddressNormalizerImpl() {} -void AddressNormalizer::LoadRulesForRegion(const std::string& region_code) { +void AddressNormalizerImpl::LoadRulesForRegion(const std::string& region_code) { address_validator_.LoadRules(region_code); } -bool AddressNormalizer::AreRulesLoadedForRegion( +bool AddressNormalizerImpl::AreRulesLoadedForRegion( const std::string& region_code) { return address_validator_.AreRulesLoadedForRegion(region_code); } -void AddressNormalizer::StartAddressNormalization( +void AddressNormalizerImpl::StartAddressNormalization( const AutofillProfile& profile, const std::string& region_code, int timeout_seconds, @@ -173,7 +173,7 @@ void AddressNormalizer::StartAddressNormalization( } } -void AddressNormalizer::OnAddressValidationRulesLoaded( +void AddressNormalizerImpl::OnAddressValidationRulesLoaded( const std::string& region_code, bool success) { // Check if an address normalization is pending. diff --git a/chromium/components/payments/core/address_normalizer_impl.h b/chromium/components/payments/core/address_normalizer_impl.h new file mode 100644 index 00000000000..1444e8ad43f --- /dev/null +++ b/chromium/components/payments/core/address_normalizer_impl.h @@ -0,0 +1,62 @@ +// Copyright 2017 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_PAYMENTS_CORE_ADDRESS_NORMALIZER_IMPL_H_ +#define COMPONENTS_PAYMENTS_CORE_ADDRESS_NORMALIZER_IMPL_H_ + +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "base/macros.h" +#include "components/payments/core/address_normalizer.h" + +namespace autofill { +class AutofillProfile; +} + +namespace i18n { +namespace addressinput { +class Source; +class Storage; +} +} + +namespace payments { + +// A class used to normalize addresses. +class AddressNormalizerImpl : public AddressNormalizer { + public: + AddressNormalizerImpl(std::unique_ptr<::i18n::addressinput::Source> source, + std::unique_ptr<::i18n::addressinput::Storage> storage); + ~AddressNormalizerImpl() override; + + // AddressNormalizer implementation. + void LoadRulesForRegion(const std::string& region_code) override; + bool AreRulesLoadedForRegion(const std::string& region_code) override; + void StartAddressNormalization(const autofill::AutofillProfile& profile, + const std::string& region_code, + int timeout_seconds, + Delegate* requester) override; + + private: + // Called when the validation rules for the |region_code| have finished + // loading. Implementation of the LoadRulesListener interface. + void OnAddressValidationRulesLoaded(const std::string& region_code, + bool success) override; + + // Map associating a region code to pending normalizations. + std::map<std::string, std::vector<std::unique_ptr<Request>>> + pending_normalization_; + + // The address validator used to normalize addresses. + autofill::AddressValidator address_validator_; + + DISALLOW_COPY_AND_ASSIGN(AddressNormalizerImpl); +}; + +} // namespace payments + +#endif // COMPONENTS_PAYMENTS_CORE_ADDRESS_NORMALIZER_IMPL_H_ diff --git a/chromium/components/payments/core/address_normalizer_unittest.cc b/chromium/components/payments/core/address_normalizer_impl_unittest.cc index da9760b3ef2..495dd04030c 100644 --- a/chromium/components/payments/core/address_normalizer_unittest.cc +++ b/chromium/components/payments/core/address_normalizer_impl_unittest.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "components/payments/core/address_normalizer.h" +#include "components/payments/core/address_normalizer_impl.h" #include <utility> @@ -79,13 +79,13 @@ class ChromiumTestdataSource : public TestdataSource { DISALLOW_COPY_AND_ASSIGN(ChromiumTestdataSource); }; -// A test subclass of the AddressNormalizer. Used to simulate rules not being -// loaded. -class TestAddressNormalizer : public AddressNormalizer { +// A test subclass of the AddressNormalizerImpl. Used to simulate rules not +// being loaded. +class TestAddressNormalizer : public AddressNormalizerImpl { public: TestAddressNormalizer(std::unique_ptr<::i18n::addressinput::Source> source, std::unique_ptr<::i18n::addressinput::Storage> storage) - : AddressNormalizer(std::move(source), std::move(storage)), + : AddressNormalizerImpl(std::move(source), std::move(storage)), should_load_rules_(true) {} ~TestAddressNormalizer() override {} @@ -96,7 +96,7 @@ class TestAddressNormalizer : public AddressNormalizer { void LoadRulesForRegion(const std::string& region_code) override { if (should_load_rules_) { - AddressNormalizer::LoadRulesForRegion(region_code); + AddressNormalizerImpl::LoadRulesForRegion(region_code); } } diff --git a/chromium/components/payments/core/autofill_payment_instrument.cc b/chromium/components/payments/core/autofill_payment_instrument.cc index 398edb70f90..82ff24d2c62 100644 --- a/chromium/components/payments/core/autofill_payment_instrument.cc +++ b/chromium/components/payments/core/autofill_payment_instrument.cc @@ -4,12 +4,17 @@ #include "components/payments/core/autofill_payment_instrument.h" +#include <memory> + #include "base/json/json_writer.h" #include "base/strings/utf_string_conversions.h" #include "base/values.h" +#include "components/autofill/core/browser/autofill_country.h" #include "components/autofill/core/browser/autofill_data_util.h" #include "components/autofill/core/browser/autofill_type.h" #include "components/autofill/core/browser/field_types.h" +#include "components/autofill/core/browser/personal_data_manager.h" +#include "components/autofill/core/browser/validation.h" #include "components/autofill/core/common/autofill_clock.h" #include "components/payments/core/basic_card_response.h" #include "components/payments/core/payment_request_data_util.h" @@ -17,20 +22,6 @@ namespace payments { -namespace { - -// Returns whether |card| has a non-empty number and cardholder name. Server -// cards will have a non-empty number. -bool CreditCardHasNumberAndName(const autofill::CreditCard& card, - const std::string& app_locale) { - return !card.number().empty() && - !card.GetInfo(autofill::AutofillType(autofill::CREDIT_CARD_NAME_FULL), - app_locale) - .empty(); -} - -} // namespace - AutofillPaymentInstrument::AutofillPaymentInstrument( const std::string& method_name, const autofill::CreditCard& card, @@ -39,11 +30,7 @@ AutofillPaymentInstrument::AutofillPaymentInstrument( PaymentRequestDelegate* payment_request_delegate) : PaymentInstrument( method_name, - /* label= */ card.TypeAndLastFourDigits(), - /* sublabel= */ - card.GetInfo(autofill::AutofillType(autofill::CREDIT_CARD_NAME_FULL), - app_locale), - autofill::data_util::GetPaymentRequestData(card.type()) + autofill::data_util::GetPaymentRequestData(card.network()) .icon_resource_id, PaymentInstrument::Type::AUTOFILL), credit_card_(card), @@ -63,21 +50,70 @@ void AutofillPaymentInstrument::InvokePaymentApp( DCHECK(!delegate_); delegate_ = delegate; + // Get the billing address. + if (!credit_card_.billing_address_id().empty()) { + autofill::AutofillProfile* billing_address = + autofill::PersonalDataManager::GetProfileFromProfilesByGUID( + credit_card_.billing_address_id(), billing_profiles_); + if (billing_address) + billing_address_ = *billing_address; + } + + is_waiting_for_billing_address_normalization_ = true; + is_waiting_for_card_unmask_ = true; + + // Start the normalization of the billing address. + // Use the country code from the profile if it is set, otherwise infer it + // from the |app_locale_|. + std::string country_code = base::UTF16ToUTF8( + billing_address_.GetRawInfo(autofill::ADDRESS_HOME_COUNTRY)); + if (!autofill::data_util::IsValidCountryCode(country_code)) { + country_code = autofill::AutofillCountry::CountryCodeForLocale(app_locale_); + } + payment_request_delegate_->GetAddressNormalizer()->StartAddressNormalization( + billing_address_, country_code, /*timeout_seconds=*/5, this); + payment_request_delegate_->DoFullCardRequest(credit_card_, weak_ptr_factory_.GetWeakPtr()); } bool AutofillPaymentInstrument::IsCompleteForPayment() { - // A card is complete for payment if it's not expired, its number is not - // empty (a server card fills this condition) and there is a cardholder name. - // TODO(crbug.com/709776): Check for billing address association. - return !credit_card_.IsExpired(autofill::AutofillClock::Now()) && - CreditCardHasNumberAndName(credit_card_, app_locale_); + // COMPLETE or EXPIRED cards are considered valid for payment. The user will + // be prompted to enter the new expiration at the CVC step. + return autofill::GetCompletionStatusForCard(credit_card_, app_locale_, + billing_profiles_) <= + autofill::CREDIT_CARD_EXPIRED; +} + +base::string16 AutofillPaymentInstrument::GetMissingInfoLabel() { + return autofill::GetCompletionMessageForCard( + autofill::GetCompletionStatusForCard(credit_card_, app_locale_, + billing_profiles_)); } bool AutofillPaymentInstrument::IsValidForCanMakePayment() { - // An expired card is still valid for the purposes of canMakePayment. - return CreditCardHasNumberAndName(credit_card_, app_locale_); + autofill::CreditCardCompletionStatus status = + autofill::GetCompletionStatusForCard(credit_card_, app_locale_, + billing_profiles_); + // Card has to have a cardholder name and number for the purposes of + // CanMakePayment. An expired card is still valid at this stage. + return !(status & autofill::CREDIT_CARD_NO_CARDHOLDER || + status & autofill::CREDIT_CARD_NO_NUMBER); +} + +void AutofillPaymentInstrument::RecordUse() { + // Record the use of the credit card. + payment_request_delegate_->GetPersonalDataManager()->RecordUseOf( + credit_card_); +} + +base::string16 AutofillPaymentInstrument::GetLabel() const { + return credit_card_.NetworkAndLastFourDigits(); +} + +base::string16 AutofillPaymentInstrument::GetSublabel() const { + return credit_card_.GetInfo( + autofill::AutofillType(autofill::CREDIT_CARD_NAME_FULL), app_locale_); } void AutofillPaymentInstrument::OnFullCardRequestSucceeded( @@ -85,19 +121,53 @@ void AutofillPaymentInstrument::OnFullCardRequestSucceeded( const base::string16& cvc) { DCHECK(delegate_); credit_card_ = card; + cvc_ = cvc; + is_waiting_for_card_unmask_ = false; + + if (!is_waiting_for_billing_address_normalization_) + GenerateBasicCardResponse(); +} + +void AutofillPaymentInstrument::OnFullCardRequestFailed() { + // The user may have cancelled the unmask or something has gone wrong (e.g., + // the network request failed). In all cases, reset the |delegate_| so another + // request can start. + delegate_ = nullptr; +} + +void AutofillPaymentInstrument::OnAddressNormalized( + const autofill::AutofillProfile& normalized_profile) { + DCHECK(is_waiting_for_billing_address_normalization_); + + billing_address_ = normalized_profile; + is_waiting_for_billing_address_normalization_ = false; + + if (!is_waiting_for_card_unmask_) + GenerateBasicCardResponse(); +} + +void AutofillPaymentInstrument::OnCouldNotNormalize( + const autofill::AutofillProfile& profile) { + // Since the phone number is formatted in either case, this profile should be + // used. + OnAddressNormalized(profile); +} + +void AutofillPaymentInstrument::GenerateBasicCardResponse() { + DCHECK(!is_waiting_for_billing_address_normalization_); + DCHECK(!is_waiting_for_card_unmask_); + DCHECK(delegate_); + std::unique_ptr<base::DictionaryValue> response_value = payments::data_util::GetBasicCardResponseFromAutofillCreditCard( - credit_card_, cvc, billing_profiles_, app_locale_) + credit_card_, cvc_, billing_address_, app_locale_) .ToDictionaryValue(); std::string stringified_details; base::JSONWriter::Write(*response_value, &stringified_details); delegate_->OnInstrumentDetailsReady(method_name(), stringified_details); - delegate_ = nullptr; -} -void AutofillPaymentInstrument::OnFullCardRequestFailed() { - // TODO(anthonyvd): Do something with the error. delegate_ = nullptr; + cvc_ = base::UTF8ToUTF16(""); } } // namespace payments diff --git a/chromium/components/payments/core/autofill_payment_instrument.h b/chromium/components/payments/core/autofill_payment_instrument.h index 84b343ab4c6..6df075f621a 100644 --- a/chromium/components/payments/core/autofill_payment_instrument.h +++ b/chromium/components/payments/core/autofill_payment_instrument.h @@ -10,14 +10,13 @@ #include <vector> #include "base/macros.h" +#include "base/strings/string16.h" +#include "components/autofill/core/browser/autofill_profile.h" #include "components/autofill/core/browser/credit_card.h" #include "components/autofill/core/browser/payments/full_card_request.h" +#include "components/payments/core/address_normalizer.h" #include "components/payments/core/payment_instrument.h" -namespace autofill { -class AutofillProfile; -} - namespace payments { class PaymentRequestDelegate; @@ -26,7 +25,8 @@ class PaymentRequestDelegate; // Request. class AutofillPaymentInstrument : public PaymentInstrument, - public autofill::payments::FullCardRequest::ResultDelegate { + public autofill::payments::FullCardRequest::ResultDelegate, + public AddressNormalizer::Delegate { public: // |billing_profiles| is owned by the caller and should outlive this object. // |payment_request_delegate| must outlive this object. @@ -41,16 +41,28 @@ class AutofillPaymentInstrument // PaymentInstrument: void InvokePaymentApp(PaymentInstrument::Delegate* delegate) override; bool IsCompleteForPayment() override; + base::string16 GetMissingInfoLabel() override; bool IsValidForCanMakePayment() override; + void RecordUse() override; + base::string16 GetLabel() const override; + base::string16 GetSublabel() const override; // autofill::payments::FullCardRequest::ResultDelegate: void OnFullCardRequestSucceeded(const autofill::CreditCard& card, const base::string16& cvc) override; void OnFullCardRequestFailed() override; + // AddressNormalizer::Delegate: + void OnAddressNormalized( + const autofill::AutofillProfile& normalized_profile) override; + void OnCouldNotNormalize(const autofill::AutofillProfile& profile) override; + autofill::CreditCard* credit_card() { return &credit_card_; } private: + // Generates the basic card response and sends it to the delegate. + void GenerateBasicCardResponse(); + // A copy of the card is owned by this object. autofill::CreditCard credit_card_; // Not owned by this object, should outlive this. @@ -60,6 +72,12 @@ class AutofillPaymentInstrument PaymentInstrument::Delegate* delegate_; PaymentRequestDelegate* payment_request_delegate_; + autofill::AutofillProfile billing_address_; + + base::string16 cvc_; + + bool is_waiting_for_card_unmask_; + bool is_waiting_for_billing_address_normalization_; base::WeakPtrFactory<AutofillPaymentInstrument> weak_ptr_factory_; diff --git a/chromium/components/payments/core/autofill_payment_instrument_unittest.cc b/chromium/components/payments/core/autofill_payment_instrument_unittest.cc index 7b9db0be6a9..7f84726ad79 100644 --- a/chromium/components/payments/core/autofill_payment_instrument_unittest.cc +++ b/chromium/components/payments/core/autofill_payment_instrument_unittest.cc @@ -5,14 +5,141 @@ #include "components/payments/core/autofill_payment_instrument.h" #include "base/macros.h" +#include "base/strings/string16.h" #include "base/strings/utf_string_conversions.h" #include "components/autofill/core/browser/autofill_profile.h" #include "components/autofill/core/browser/autofill_test_utils.h" #include "components/autofill/core/browser/credit_card.h" +#include "components/payments/core/address_normalizer.h" +#include "components/payments/core/test_payment_request_delegate.h" +#include "components/strings/grit/components_strings.h" #include "testing/gtest/include/gtest/gtest.h" +#include "ui/base/l10n/l10n_util.h" namespace payments { +namespace { + +class FakePaymentInstrumentDelegate : public PaymentInstrument::Delegate { + public: + FakePaymentInstrumentDelegate() {} + + void OnInstrumentDetailsReady( + const std::string& method_name, + const std::string& stringified_details) override { + on_instrument_details_ready_called_ = true; + } + + void OnInstrumentDetailsError() override { + on_instrument_details_error_called_ = true; + } + + bool WasOnInstrumentDetailsReadyCalled() { + return on_instrument_details_ready_called_; + } + + bool WasOnInstrumentDetailsErrorCalled() { + return on_instrument_details_error_called_; + } + + private: + bool on_instrument_details_ready_called_ = false; + bool on_instrument_details_error_called_ = false; +}; + +class FakeAddressNormalizer : public AddressNormalizer { + public: + FakeAddressNormalizer() {} + + void LoadRulesForRegion(const std::string& region_code) override {} + + bool AreRulesLoadedForRegion(const std::string& region_code) override { + return true; + } + + void StartAddressNormalization( + const autofill::AutofillProfile& profile, + const std::string& region_code, + int timeout_seconds, + AddressNormalizer::Delegate* requester) override { + profile_ = profile; + requester_ = requester; + } + + void OnAddressValidationRulesLoaded(const std::string& region_code, + bool success) override {} + + void CompleteAddressNormalization() { + requester_->OnAddressNormalized(profile_); + } + + private: + autofill::AutofillProfile profile_; + AddressNormalizer::Delegate* requester_; +}; + +class FakePaymentRequestDelegate : public PaymentRequestDelegate { + public: + FakePaymentRequestDelegate() + : locale_("en-US"), last_committed_url_("https://shop.com") {} + void ShowDialog(PaymentRequest* request) override {} + + void CloseDialog() override {} + + void ShowErrorMessage() override {} + + autofill::PersonalDataManager* GetPersonalDataManager() override { + return nullptr; + } + + const std::string& GetApplicationLocale() const override { return locale_; } + + bool IsIncognito() const override { return false; } + + bool IsSslCertificateValid() override { return true; } + + const GURL& GetLastCommittedURL() const override { + return last_committed_url_; + } + + void DoFullCardRequest( + const autofill::CreditCard& credit_card, + base::WeakPtr<autofill::payments::FullCardRequest::ResultDelegate> + result_delegate) override { + full_card_request_card_ = credit_card; + full_card_result_delegate_ = result_delegate; + } + + AddressNormalizer* GetAddressNormalizer() override { + return &address_normalizer_; + } + + FakeAddressNormalizer* GetTestAddressNormalizer() { + return &address_normalizer_; + } + + void CompleteFullCardRequest() { + full_card_result_delegate_->OnFullCardRequestSucceeded( + full_card_request_card_, base::ASCIIToUTF16("123")); + } + + autofill::RegionDataLoader* GetRegionDataLoader() override { return nullptr; } + + ukm::UkmRecorder* GetUkmRecorder() override { return nullptr; } + + private: + std::string locale_; + const GURL last_committed_url_; + FakeAddressNormalizer address_normalizer_; + + autofill::CreditCard full_card_request_card_; + base::WeakPtr<autofill::payments::FullCardRequest::ResultDelegate> + full_card_result_delegate_; + DISALLOW_COPY_AND_ASSIGN(FakePaymentRequestDelegate); +}; + +} // namespace + class AutofillPaymentInstrumentTest : public testing::Test { protected: AutofillPaymentInstrumentTest() @@ -40,15 +167,17 @@ TEST_F(AutofillPaymentInstrumentTest, IsCompleteForPayment) { AutofillPaymentInstrument instrument("visa", local_credit_card(), billing_profiles(), "en-US", nullptr); EXPECT_TRUE(instrument.IsCompleteForPayment()); + EXPECT_TRUE(instrument.GetMissingInfoLabel().empty()); } -// An expired local card is not a valid instrument for payment. +// An expired local card is still a valid instrument for payment. TEST_F(AutofillPaymentInstrumentTest, IsCompleteForPayment_Expired) { autofill::CreditCard& card = local_credit_card(); card.SetExpirationYear(2016); // Expired. AutofillPaymentInstrument instrument("visa", card, billing_profiles(), "en-US", nullptr); - EXPECT_FALSE(instrument.IsCompleteForPayment()); + EXPECT_TRUE(instrument.IsCompleteForPayment()); + EXPECT_EQ(base::string16(), instrument.GetMissingInfoLabel()); } // A local card with no name is not a valid instrument for payment. @@ -56,35 +185,92 @@ TEST_F(AutofillPaymentInstrumentTest, IsCompleteForPayment_NoName) { autofill::CreditCard& card = local_credit_card(); card.SetInfo(autofill::AutofillType(autofill::CREDIT_CARD_NAME_FULL), base::ASCIIToUTF16(""), "en-US"); + base::string16 missing_info; AutofillPaymentInstrument instrument("visa", card, billing_profiles(), "en-US", nullptr); EXPECT_FALSE(instrument.IsCompleteForPayment()); + EXPECT_EQ(l10n_util::GetStringUTF16(IDS_PAYMENTS_NAME_ON_CARD_REQUIRED), + instrument.GetMissingInfoLabel()); } // A local card with no name is not a valid instrument for payment. TEST_F(AutofillPaymentInstrumentTest, IsCompleteForPayment_NoNumber) { autofill::CreditCard& card = local_credit_card(); card.SetNumber(base::ASCIIToUTF16("")); + base::string16 missing_info; + AutofillPaymentInstrument instrument("visa", card, billing_profiles(), + "en-US", nullptr); + EXPECT_FALSE(instrument.IsCompleteForPayment()); + EXPECT_EQ(l10n_util::GetStringUTF16( + IDS_PAYMENTS_CARD_NUMBER_INVALID_VALIDATION_MESSAGE), + instrument.GetMissingInfoLabel()); +} + +// A local card with no billing address id is not a valid instrument for +// payment. +TEST_F(AutofillPaymentInstrumentTest, IsCompleteForPayment_NoBillinbAddressId) { + autofill::CreditCard& card = local_credit_card(); + card.set_billing_address_id(""); + base::string16 missing_info; + AutofillPaymentInstrument instrument("visa", card, billing_profiles(), + "en-US", nullptr); + EXPECT_FALSE(instrument.IsCompleteForPayment()); + EXPECT_EQ( + l10n_util::GetStringUTF16(IDS_PAYMENTS_CARD_BILLING_ADDRESS_REQUIRED), + instrument.GetMissingInfoLabel()); +} + +// A local card with an invalid billing address id is not a valid instrument for +// payment. +TEST_F(AutofillPaymentInstrumentTest, + IsCompleteForPayment_InvalidBillinbAddressId) { + autofill::CreditCard& card = local_credit_card(); + card.set_billing_address_id("InvalidBillingAddressId"); + base::string16 missing_info; AutofillPaymentInstrument instrument("visa", card, billing_profiles(), "en-US", nullptr); EXPECT_FALSE(instrument.IsCompleteForPayment()); + EXPECT_EQ( + l10n_util::GetStringUTF16(IDS_PAYMENTS_CARD_BILLING_ADDRESS_REQUIRED), + instrument.GetMissingInfoLabel()); +} + +// A local card with no name and no number is not a valid instrument for +// payment. +TEST_F(AutofillPaymentInstrumentTest, + IsCompleteForPayment_MultipleThingsMissing) { + autofill::CreditCard& card = local_credit_card(); + card.SetNumber(base::ASCIIToUTF16("")); + card.SetInfo(autofill::AutofillType(autofill::CREDIT_CARD_NAME_FULL), + base::ASCIIToUTF16(""), "en-US"); + AutofillPaymentInstrument instrument("visa", card, billing_profiles(), + "en-US", nullptr); + EXPECT_FALSE(instrument.IsCompleteForPayment()); + EXPECT_EQ(l10n_util::GetStringUTF16(IDS_PAYMENTS_MORE_INFORMATION_REQUIRED), + instrument.GetMissingInfoLabel()); } // A Masked (server) card is a valid instrument for payment. TEST_F(AutofillPaymentInstrumentTest, IsCompleteForPayment_MaskedCard) { autofill::CreditCard card = autofill::test::GetMaskedServerCard(); + ASSERT_GT(billing_profiles().size(), 0UL); + card.set_billing_address_id(billing_profiles()[0]->guid()); AutofillPaymentInstrument instrument("visa", card, billing_profiles(), "en-US", nullptr); EXPECT_TRUE(instrument.IsCompleteForPayment()); + EXPECT_TRUE(instrument.GetMissingInfoLabel().empty()); } -// An expired masked (server) card is not a valid instrument for payment. +// An expired masked (server) card is still a valid instrument for payment. TEST_F(AutofillPaymentInstrumentTest, IsCompleteForPayment_ExpiredMaskedCard) { autofill::CreditCard card = autofill::test::GetMaskedServerCard(); + ASSERT_GT(billing_profiles().size(), 0UL); + card.set_billing_address_id(billing_profiles()[0]->guid()); card.SetExpirationYear(2016); // Expired. AutofillPaymentInstrument instrument("visa", card, billing_profiles(), "en-US", nullptr); - EXPECT_FALSE(instrument.IsCompleteForPayment()); + EXPECT_TRUE(instrument.IsCompleteForPayment()); + EXPECT_EQ(base::string16(), instrument.GetMissingInfoLabel()); } // An expired card is a valid instrument for canMakePayment. @@ -124,4 +310,60 @@ TEST_F(AutofillPaymentInstrumentTest, IsValidForCanMakePayment_NoNumber) { EXPECT_FALSE(instrument.IsValidForCanMakePayment()); } +// Tests that the autofill instrument only calls OnInstrumentDetailsReady when +// the billing address has been normalized and the card has been unmasked. +TEST_F(AutofillPaymentInstrumentTest, + InvokePaymentApp_NormalizationBeforeUnmask) { + TestPaymentRequestDelegate delegate(/*personal_data_manager=*/nullptr); + delegate.DelayFullCardRequestCompletion(); + delegate.test_address_normalizer()->DelayNormalization(); + + autofill::CreditCard& card = local_credit_card(); + card.SetNumber(base::ASCIIToUTF16("")); + AutofillPaymentInstrument instrument("visa", card, billing_profiles(), + "en-US", &delegate); + + FakePaymentInstrumentDelegate instrument_delegate; + + instrument.InvokePaymentApp(&instrument_delegate); + EXPECT_FALSE(instrument_delegate.WasOnInstrumentDetailsReadyCalled()); + EXPECT_FALSE(instrument_delegate.WasOnInstrumentDetailsErrorCalled()); + + delegate.test_address_normalizer()->CompleteAddressNormalization(); + EXPECT_FALSE(instrument_delegate.WasOnInstrumentDetailsReadyCalled()); + EXPECT_FALSE(instrument_delegate.WasOnInstrumentDetailsErrorCalled()); + + delegate.CompleteFullCardRequest(); + EXPECT_TRUE(instrument_delegate.WasOnInstrumentDetailsReadyCalled()); + EXPECT_FALSE(instrument_delegate.WasOnInstrumentDetailsErrorCalled()); +} + +// Tests that the autofill instrument only calls OnInstrumentDetailsReady when +// the billing address has been normalized and the card has been unmasked. +TEST_F(AutofillPaymentInstrumentTest, + InvokePaymentApp_UnmaskBeforeNormalization) { + TestPaymentRequestDelegate delegate(/*personal_data_manager=*/nullptr); + delegate.DelayFullCardRequestCompletion(); + delegate.test_address_normalizer()->DelayNormalization(); + + autofill::CreditCard& card = local_credit_card(); + card.SetNumber(base::ASCIIToUTF16("")); + AutofillPaymentInstrument instrument("visa", card, billing_profiles(), + "en-US", &delegate); + + FakePaymentInstrumentDelegate instrument_delegate; + + instrument.InvokePaymentApp(&instrument_delegate); + EXPECT_FALSE(instrument_delegate.WasOnInstrumentDetailsReadyCalled()); + EXPECT_FALSE(instrument_delegate.WasOnInstrumentDetailsErrorCalled()); + + delegate.CompleteFullCardRequest(); + EXPECT_FALSE(instrument_delegate.WasOnInstrumentDetailsReadyCalled()); + EXPECT_FALSE(instrument_delegate.WasOnInstrumentDetailsErrorCalled()); + + delegate.test_address_normalizer()->CompleteAddressNormalization(); + EXPECT_TRUE(instrument_delegate.WasOnInstrumentDetailsReadyCalled()); + EXPECT_FALSE(instrument_delegate.WasOnInstrumentDetailsErrorCalled()); +} + } // namespace payments diff --git a/chromium/components/payments/core/can_make_payment_query.cc b/chromium/components/payments/core/can_make_payment_query.cc new file mode 100644 index 00000000000..c4264be89be --- /dev/null +++ b/chromium/components/payments/core/can_make_payment_query.cc @@ -0,0 +1,44 @@ +// Copyright 2017 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/payments/core/can_make_payment_query.h" + +#include <utility> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/location.h" +#include "base/memory/ptr_util.h" +#include "base/time/time.h" + +namespace payments { + +CanMakePaymentQuery::CanMakePaymentQuery() {} + +CanMakePaymentQuery::~CanMakePaymentQuery() {} + +bool CanMakePaymentQuery::CanQuery( + const GURL& frame_origin, + const std::map<std::string, std::set<std::string>>& query) { + const auto& it = queries_.find(frame_origin); + if (it == queries_.end()) { + std::unique_ptr<base::OneShotTimer> timer = + base::MakeUnique<base::OneShotTimer>(); + timer->Start(FROM_HERE, base::TimeDelta::FromMinutes(30), + base::Bind(&CanMakePaymentQuery::ExpireQuotaForFrameOrigin, + base::Unretained(this), frame_origin)); + timers_.insert(std::make_pair(frame_origin, std::move(timer))); + queries_.insert(std::make_pair(frame_origin, query)); + return true; + } + + return it->second == query; +} + +void CanMakePaymentQuery::ExpireQuotaForFrameOrigin(const GURL& frame_origin) { + timers_.erase(frame_origin); + queries_.erase(frame_origin); +} + +} // namespace payments diff --git a/chromium/components/payments/core/can_make_payment_query.h b/chromium/components/payments/core/can_make_payment_query.h new file mode 100644 index 00000000000..69da5761453 --- /dev/null +++ b/chromium/components/payments/core/can_make_payment_query.h @@ -0,0 +1,50 @@ +// Copyright 2017 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_PAYMENTS_CORE_CAN_MAKE_PAYMENT_QUERY_H_ +#define COMPONENTS_PAYMENTS_CORE_CAN_MAKE_PAYMENT_QUERY_H_ + +#include <map> +#include <memory> +#include <set> +#include <string> + +#include "base/macros.h" +#include "base/timer/timer.h" +#include "components/keyed_service/core/keyed_service.h" +#include "url/gurl.h" + +namespace payments { + +// Keeps track of canMakePayment() queries per browser context. +class CanMakePaymentQuery : public KeyedService { + public: + CanMakePaymentQuery(); + ~CanMakePaymentQuery() override; + + // Returns whether |frame_origin| can call canMakePayment() with |query|, + // which is a mapping of payment method names to the corresponding + // JSON-stringified payment method data. Remembers the frame-to-query mapping + // for 30 minutes to enforce the quota. + bool CanQuery(const GURL& frame_origin, + const std::map<std::string, std::set<std::string>>& query); + + private: + void ExpireQuotaForFrameOrigin(const GURL& frame_origin); + + // A mapping of frame origin to the timer that, when fired, allows the frame + // to invoke canMakePayment() with a different query. + std::map<GURL, std::unique_ptr<base::OneShotTimer>> timers_; + + // A mapping of frame origin to its last query. Each query is a mapping of + // payment method names to the corresponding JSON-stringified payment method + // data. + std::map<GURL, std::map<std::string, std::set<std::string>>> queries_; + + DISALLOW_COPY_AND_ASSIGN(CanMakePaymentQuery); +}; + +} // namespace payments + +#endif // COMPONENTS_PAYMENTS_CORE_CAN_MAKE_PAYMENT_QUERY_H_ diff --git a/chromium/components/payments/core/currency_formatter.cc b/chromium/components/payments/core/currency_formatter.cc index 4eb4f74ea06..dae2383c932 100644 --- a/chromium/components/payments/core/currency_formatter.cc +++ b/chromium/components/payments/core/currency_formatter.cc @@ -104,19 +104,23 @@ base::string16 CurrencyFormatter::Format(const std::string& amount) { if (output.isEmpty()) return base::UTF8ToUTF16(amount); - // Explicitly removes the currency code (truncated to its 3-letter and - // 2-letter versions) from the output, because callers are expected to + // Explicitly removes the currency code (truncated to its 3-letter, 2-letter + // and 1-letter versions) from the output, because callers are expected to // display the currency code alongside this result. // // 3+ letters: If currency code is "ABCDEF" or "BTX", this code will // transform "ABC55.00"/"BTX55.00" to "55.00". // 2 letters: If currency code is "CAD", this code will transform "CA$55.00" // to "$55.00" (en_US) or "55,00 $ CA" to "55,00 $" (fr_FR). + // 1 letter: If currency code is "AUD", this code will transform "A$55.00" + // to "$55.00" (en_US). icu::UnicodeString tmp_currency_code(*currency_code_); tmp_currency_code.truncate(3); output.findAndReplace(tmp_currency_code, ""); tmp_currency_code.truncate(2); output.findAndReplace(tmp_currency_code, ""); + tmp_currency_code.truncate(1); + output.findAndReplace(tmp_currency_code, ""); // Trims any unicode whitespace (including non-breaking space). if (u_isUWhiteSpace(output[0])) { output.remove(0, 1); diff --git a/chromium/components/payments/core/currency_formatter_unittest.cc b/chromium/components/payments/core/currency_formatter_unittest.cc index e55a2fe29a5..41793ef4c0c 100644 --- a/chromium/components/payments/core/currency_formatter_unittest.cc +++ b/chromium/components/payments/core/currency_formatter_unittest.cc @@ -81,6 +81,11 @@ INSTANTIATE_TEST_CASE_P( TestCase("55.00", "CAD", "fr_CA", "55,00 $", "CAD"), TestCase("55.00", "CAD", "fr_FR", "55,00 $", "CAD"), + TestCase("55.00", "AUD", "en_US", "$55.00", "AUD"), + TestCase("55.00", "AUD", "en_CA", "$55.00", "AUD"), + TestCase("55.00", "AUD", "fr_CA", "55,00 $", "AUD"), + TestCase("55.00", "AUD", "fr_FR", "55,00 $", "AUD"), + TestCase("55.00", "BRL", "en_US", "R$55.00", "BRL"), TestCase("55.00", "BRL", "fr_CA", "55,00 R$", "BRL"), TestCase("55.00", "BRL", "pt_BR", "R$55,00", "BRL"), @@ -119,8 +124,12 @@ INSTANTIATE_TEST_CASE_P( "USD", "fr_FR", "123Â 456Â 789Â 012Â 345Â 678Â 901Â 234Â 567Â 890,123456789 $", - "USD"), + "USD"))); +INSTANTIATE_TEST_CASE_P( + CurrencySystems, + PaymentsCurrencyFormatterTest, + testing::Values( // When the currency system is not ISO4217, only the amount is formatted // using the locale (there is no other indication of currency). TestCase("55.00", diff --git a/chromium/components/payments/core/journey_logger.cc b/chromium/components/payments/core/journey_logger.cc index dc25cf6f1bf..7eee988f1be 100644 --- a/chromium/components/payments/core/journey_logger.cc +++ b/chromium/components/payments/core/journey_logger.cc @@ -8,9 +8,8 @@ #include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" -#include "components/autofill/core/browser/autofill_experiments.h" -#include "components/ukm/ukm_entry_builder.h" -#include "components/ukm/ukm_service.h" +#include "components/ukm/public/ukm_entry_builder.h" +#include "components/ukm/public/ukm_recorder.h" namespace payments { @@ -66,16 +65,16 @@ std::string GetHistogramNameSuffix( JourneyLogger::JourneyLogger(bool is_incognito, const GURL& url, - ukm::UkmService* ukm_service) - : was_can_make_payments_used_(false), - could_make_payment_(false), - was_show_called_(false), - is_incognito_(is_incognito), + ukm::UkmRecorder* ukm_recorder) + : is_incognito_(is_incognito), events_(EVENT_INITIATED), url_(url), - ukm_service_(ukm_service) {} + ukm_recorder_(ukm_recorder) {} -JourneyLogger::~JourneyLogger() {} +JourneyLogger::~JourneyLogger() { + if (was_show_called_) + DCHECK(has_recorded_); +} void JourneyLogger::IncrementSelectionAdds(Section section) { DCHECK_LT(section, SECTION_MAX); @@ -107,12 +106,68 @@ void JourneyLogger::SetShowCalled() { was_show_called_ = true; } +void JourneyLogger::SetCompleted() { + UMA_HISTOGRAM_BOOLEAN("PaymentRequest.CheckoutFunnel.Completed", true); + + RecordJourneyStatsHistograms(COMPLETION_STATUS_COMPLETED); +} + +void JourneyLogger::SetAborted(AbortReason reason) { + base::UmaHistogramEnumeration("PaymentRequest.CheckoutFunnel.Aborted", reason, + ABORT_REASON_MAX); + + if (reason == ABORT_REASON_ABORTED_BY_USER || + reason == ABORT_REASON_USER_NAVIGATION) + RecordJourneyStatsHistograms(COMPLETION_STATUS_USER_ABORTED); + else + RecordJourneyStatsHistograms(COMPLETION_STATUS_OTHER_ABORTED); +} + +#ifdef OS_ANDROID +void JourneyLogger::SetNotShown(NotShownReason reason) { + base::UmaHistogramEnumeration("PaymentRequest.CheckoutFunnel.NoShow", reason, + NOT_SHOWN_REASON_MAX); + + // Record that that Payment Request was initiated here, because nothing else + // will be recorded for a Payment Request that was not shown to the user. + UMA_HISTOGRAM_BOOLEAN("PaymentRequest.CheckoutFunnel.Initiated", true); +} +#endif + void JourneyLogger::SetEventOccurred(Event event) { events_ |= event; } +void JourneyLogger::SetSelectedPaymentMethod( + SelectedPaymentMethod payment_method) { + payment_method_ = payment_method; +} + +void JourneyLogger::SetRequestedInformation(bool requested_shipping, + bool requested_email, + bool requested_phone, + bool requested_name) { + // This method should only be called once per Payment Request. + DCHECK(requested_information_ == REQUESTED_INFORMATION_MAX); + + requested_information_ = + (requested_shipping ? REQUESTED_INFORMATION_SHIPPING : 0) | + (requested_email ? REQUESTED_INFORMATION_EMAIL : 0) | + (requested_phone ? REQUESTED_INFORMATION_PHONE : 0) | + (requested_name ? REQUESTED_INFORMATION_NAME : 0); +} + void JourneyLogger::RecordJourneyStatsHistograms( CompletionStatus completion_status) { + DCHECK(!has_recorded_); + has_recorded_ = true; + + RecordCheckoutFlowMetrics(); + + RecordPaymentMethodMetric(); + + RecordRequestedInformationMetrics(); + RecordSectionSpecificStats(completion_status); // Record the CanMakePayment metrics based on whether the transaction was @@ -122,6 +177,33 @@ void JourneyLogger::RecordJourneyStatsHistograms( RecordUrlKeyedMetrics(completion_status); } +void JourneyLogger::RecordCheckoutFlowMetrics() { + UMA_HISTOGRAM_BOOLEAN("PaymentRequest.CheckoutFunnel.Initiated", true); + + if (events_ & EVENT_SHOWN) + UMA_HISTOGRAM_BOOLEAN("PaymentRequest.CheckoutFunnel.Shown", true); + + if (events_ & EVENT_PAY_CLICKED) + UMA_HISTOGRAM_BOOLEAN("PaymentRequest.CheckoutFunnel.PayClicked", true); + + if (events_ & EVENT_RECEIVED_INSTRUMENT_DETAILS) + UMA_HISTOGRAM_BOOLEAN( + "PaymentRequest.CheckoutFunnel.ReceivedInstrumentDetails", true); + + if (events_ & EVENT_SKIPPED_SHOW) + UMA_HISTOGRAM_BOOLEAN("PaymentRequest.CheckoutFunnel.SkippedShow", true); +} + +void JourneyLogger::RecordPaymentMethodMetric() { + base::UmaHistogramEnumeration("PaymentRequest.SelectedPaymentMethod", + payment_method_, SELECTED_PAYMENT_METHOD_MAX); +} + +void JourneyLogger::RecordRequestedInformationMetrics() { + UMA_HISTOGRAM_ENUMERATION("PaymentRequest.RequestedInformation", + requested_information_, REQUESTED_INFORMATION_MAX); +} + void JourneyLogger::RecordSectionSpecificStats( CompletionStatus completion_status) { // Record whether the user had suggestions for each requested information. @@ -194,12 +276,12 @@ void JourneyLogger::RecordCanMakePaymentEffectOnShow() { int effect_on_show = 0; if (was_show_called_) - effect_on_show |= CMP_SHOW_DID_SHOW; + effect_on_show |= CMP_EFFECT_ON_SHOW_DID_SHOW; if (could_make_payment_) - effect_on_show |= CMP_SHOW_COULD_MAKE_PAYMENT; + effect_on_show |= CMP_EFFECT_ON_SHOW_COULD_MAKE_PAYMENT; UMA_HISTOGRAM_ENUMERATION("PaymentRequest.CanMakePayment.Used.EffectOnShow", - effect_on_show, CMP_SHOW_MAX); + effect_on_show, CMP_EFFECT_ON_SHOW_MAX); } void JourneyLogger::RecordCanMakePaymentEffectOnCompletion( @@ -221,14 +303,15 @@ void JourneyLogger::RecordCanMakePaymentEffectOnCompletion( } void JourneyLogger::RecordUrlKeyedMetrics(CompletionStatus completion_status) { - if (!autofill::IsUkmLoggingEnabled() || !ukm_service_ || !url_.is_valid()) + if (!ukm_recorder_ || !url_.is_valid()) return; // Record the Checkout Funnel UKM. - int32_t source_id = ukm_service_->GetNewSourceID(); - ukm_service_->UpdateSourceURL(source_id, url_); - std::unique_ptr<ukm::UkmEntryBuilder> builder = ukm_service_->GetEntryBuilder( - source_id, internal::kUKMCheckoutEventsEntryName); + ukm::SourceId source_id = ukm_recorder_->GetNewSourceID(); + ukm_recorder_->UpdateSourceURL(source_id, url_); + std::unique_ptr<ukm::UkmEntryBuilder> builder = + ukm_recorder_->GetEntryBuilder(source_id, + internal::kUKMCheckoutEventsEntryName); builder->AddMetric(internal::kUKMCompletionStatusMetricName, completion_status); builder->AddMetric(internal::kUKMEventsMetricName, events_); diff --git a/chromium/components/payments/core/journey_logger.h b/chromium/components/payments/core/journey_logger.h index c3330eb8540..b37f7ae48dd 100644 --- a/chromium/components/payments/core/journey_logger.h +++ b/chromium/components/payments/core/journey_logger.h @@ -11,7 +11,7 @@ #include "url/gurl.h" namespace ukm { -class UkmService; +class UkmRecorder; } namespace payments { @@ -30,11 +30,12 @@ extern const char kUKMEventsMetricName[]; // of the Payment Request. class JourneyLogger { public: - // Note: These constants should always be in sync with their counterpart in - // components/payments/content/android/java/src/org/chromium/components/ - // payments/JourneyLogger.java. + // Note: Java counterparts will be generated for these enums. + // The different sections of a Payment Request. Used to record journey // stats. + // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.payments + // GENERATED_JAVA_CLASS_NAME_OVERRIDE: Section enum Section { SECTION_CONTACT_INFO = 0, SECTION_CREDIT_CARDS = 1, @@ -43,14 +44,51 @@ class JourneyLogger { }; // For the CanMakePayment histograms. + // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.payments + // GENERATED_JAVA_CLASS_NAME_OVERRIDE: CanMakePaymentUsage enum CanMakePaymentUsage { CAN_MAKE_PAYMENT_USED = 0, CAN_MAKE_PAYMENT_NOT_USED = 1, CAN_MAKE_PAYMENT_USE_MAX, }; + // The information requested by the merchant. + // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.payments + // GENERATED_JAVA_CLASS_NAME_OVERRIDE: RequestedInformation + enum RequestedInformation { + REQUESTED_INFORMATION_NONE = 0, + REQUESTED_INFORMATION_EMAIL = 1 << 0, + REQUESTED_INFORMATION_PHONE = 1 << 1, + REQUESTED_INFORMATION_SHIPPING = 1 << 2, + REQUESTED_INFORMATION_NAME = 1 << 3, + REQUESTED_INFORMATION_MAX = 16, + }; + + // The payment method that was used by the user to complete the transaction. + // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.payments + // GENERATED_JAVA_CLASS_NAME_OVERRIDE: SelectedPaymentMethod + enum SelectedPaymentMethod { + SELECTED_PAYMENT_METHOD_CREDIT_CARD = 0, + SELECTED_PAYMENT_METHOD_ANDROID_PAY = 1, + SELECTED_PAYMENT_METHOD_OTHER_PAYMENT_APP = 2, + SELECTED_PAYMENT_METHOD_MAX = 3, + }; + + // Used to mesure the impact of the CanMakePayment return value on whether the + // Payment Request is shown to the user. + // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.payments + // GENERATED_JAVA_CLASS_NAME_OVERRIDE: CanMakePaymentEffectOnShow + enum CmpEffectOnShow { + CMP_EFFECT_ON_SHOW_COULD_NOT_MAKE_PAYMENT_AND_DID_NOT_SHOW = 0, + CMP_EFFECT_ON_SHOW_DID_SHOW = 1 << 0, + CMP_EFFECT_ON_SHOW_COULD_MAKE_PAYMENT = 1 << 1, + CMP_EFFECT_ON_SHOW_MAX = 4, + }; + // Used to log different parameters' effect on whether the transaction was // completed. + // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.payments + // GENERATED_JAVA_CLASS_NAME_OVERRIDE: CompletionStatus enum CompletionStatus { COMPLETION_STATUS_COMPLETED = 0, COMPLETION_STATUS_USER_ABORTED = 1, @@ -60,25 +98,51 @@ class JourneyLogger { // Used to record the different events that happened during the Payment // Request. + // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.payments + // GENERATED_JAVA_CLASS_NAME_OVERRIDE: Event enum Event { EVENT_INITIATED = 0, EVENT_SHOWN = 1 << 0, EVENT_PAY_CLICKED = 1 << 1, EVENT_RECEIVED_INSTRUMENT_DETAILS = 1 << 2, EVENT_SKIPPED_SHOW = 1 << 3, - EVENT_MAX = 16, + EVENT_ENUM_MAX = 16, }; - // Used to mesure the impact of the CanMakePayment return value on whether the - // Payment Request is shown to the user. - static const int CMP_SHOW_COULD_NOT_MAKE_PAYMENT_AND_DID_NOT_SHOW = 0; - static const int CMP_SHOW_DID_SHOW = 1 << 0; - static const int CMP_SHOW_COULD_MAKE_PAYMENT = 1 << 1; - static const int CMP_SHOW_MAX = 4; + // The reason why the Payment Request was aborted. + // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.payments + // GENERATED_JAVA_CLASS_NAME_OVERRIDE: AbortReason + enum AbortReason { + ABORT_REASON_ABORTED_BY_USER = 0, + ABORT_REASON_ABORTED_BY_MERCHANT = 1, + ABORT_REASON_INVALID_DATA_FROM_RENDERER = 2, + ABORT_REASON_MOJO_CONNECTION_ERROR = 3, + ABORT_REASON_MOJO_RENDERER_CLOSING = 4, + ABORT_REASON_INSTRUMENT_DETAILS_ERROR = 5, + ABORT_REASON_NO_MATCHING_PAYMENT_METHOD = 6, // Deprecated. + ABORT_REASON_NO_SUPPORTED_PAYMENT_METHOD = 7, // Deprecated. + ABORT_REASON_OTHER = 8, + ABORT_REASON_USER_NAVIGATION = 9, + ABORT_REASON_MERCHANT_NAVIGATION = 10, + ABORT_REASON_MAX, + }; + +#ifdef OS_ANDROID + // The reason why the Payment Request was not shown to the user. + // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.payments + // GENERATED_JAVA_CLASS_NAME_OVERRIDE: NotShownReason + enum NotShownReason { + NOT_SHOWN_REASON_NO_MATCHING_PAYMENT_METHOD = 0, + NOT_SHOWN_REASON_NO_SUPPORTED_PAYMENT_METHOD = 1, + NOT_SHOWN_REASON_CONCURRENT_REQUESTS = 2, + NOT_SHOWN_REASON_OTHER = 3, + NOT_SHOWN_REASON_MAX = 4, + }; +#endif JourneyLogger(bool is_incognito, const GURL& url, - ukm::UkmService* ukm_service); + ukm::UkmRecorder* ukm_recorder); ~JourneyLogger(); // Increments the number of selection adds for the specified section. @@ -103,11 +167,28 @@ class JourneyLogger { // Records that an event occurred. void SetEventOccurred(Event event); - // Records the histograms for all the sections that were requested by the - // merchant and for the usage of the CanMakePayment method and its effect on - // the transaction. This method should be called when the Payment Request has - // either been completed or aborted. - void RecordJourneyStatsHistograms(CompletionStatus completion_status); + // Records the payment method that was used to complete the Payment Request. + void SetSelectedPaymentMethod(SelectedPaymentMethod payment_method); + + // Records the user information requested by the merchant. + void SetRequestedInformation(bool requested_shipping, + bool requested_email, + bool requested_phone, + bool requested_name); + + // Records that the Payment Request was completed successfully, and starts the + // logging of all the journey metrics. + void SetCompleted(); + + // Records that the Payment Request was aborted along with the reason. Also + // starts the logging of all the journey metrics. + void SetAborted(AbortReason reason); + +#ifdef OS_ANDROID + // Records that the Payment Request was not shown to the user, along with the + // reason. + void SetNotShown(NotShownReason reason); +#endif private: static const int NUMBER_OF_SECTIONS = 3; @@ -137,6 +218,22 @@ class JourneyLogger { }; // Records the histograms for all the sections that were requested by the + // merchant and for the usage of the CanMakePayment method and its effect on + // the transaction. This method should be called when the Payment Request has + // either been completed or aborted. + void RecordJourneyStatsHistograms(CompletionStatus completion_status); + + // Records the histograms for all the steps of a complete checkout flow that + // were reached. + void RecordCheckoutFlowMetrics(); + + // Records the metric about the selected payment method. + void RecordPaymentMethodMetric(); + + // Records the user information that the merchant requested. + void RecordRequestedInformationMetrics(); + + // Records the histograms for all the sections that were requested by the // merchant. void RecordSectionSpecificStats(CompletionStatus completion_status); @@ -157,18 +254,25 @@ class JourneyLogger { void RecordUrlKeyedMetrics(CompletionStatus completion_status); SectionStats sections_[NUMBER_OF_SECTIONS]; - bool was_can_make_payments_used_; - bool could_make_payment_; - bool was_show_called_; + bool has_recorded_ = false; + bool was_can_make_payments_used_ = false; + bool could_make_payment_ = false; + bool was_show_called_ = false; bool is_incognito_; // Accumulates the many events that have happened during the Payment Request. int events_; + // To keep track of the selected payment method. + SelectedPaymentMethod payment_method_ = SELECTED_PAYMENT_METHOD_MAX; + + // Keeps track of the user information requested by the merchant. + int requested_information_ = REQUESTED_INFORMATION_MAX; + const GURL url_; // Not owned, will outlive this object. - ukm::UkmService* ukm_service_; + ukm::UkmRecorder* ukm_recorder_; DISALLOW_COPY_AND_ASSIGN(JourneyLogger); }; diff --git a/chromium/components/payments/core/journey_logger_unittest.cc b/chromium/components/payments/core/journey_logger_unittest.cc index fbb1efd4cbd..941fccb580d 100644 --- a/chromium/components/payments/core/journey_logger_unittest.cc +++ b/chromium/components/payments/core/journey_logger_unittest.cc @@ -7,10 +7,8 @@ #include "base/metrics/metrics_hashes.h" #include "base/test/histogram_tester.h" #include "base/test/scoped_feature_list.h" -#include "components/autofill/core/browser/autofill_experiments.h" #include "components/metrics/proto/ukm/entry.pb.h" -#include "components/ukm/test_ukm_service.h" -#include "components/ukm/ukm_entry.h" +#include "components/ukm/test_ukm_recorder.h" #include "components/ukm/ukm_source.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -19,29 +17,15 @@ using testing::ContainerEq; namespace payments { -namespace { -// Finds the specified UKM metric by |name| in the specified UKM |metrics|. -const ukm::Entry_Metric* FindMetric( - const char* name, - const google::protobuf::RepeatedPtrField<ukm::Entry_Metric>& metrics) { - for (const auto& metric : metrics) { - if (metric.metric_hash() == base::HashMetricName(name)) - return &metric; - } - return nullptr; -} -} // namespace - // Tests the canMakePayment stats for the case where the merchant does not use // it and does not show the PaymentRequest to the user. TEST(JourneyLoggerTest, RecordJourneyStatsHistograms_CanMakePaymentNotCalled_NoShow) { base::HistogramTester histogram_tester; JourneyLogger logger(/*is_incognito=*/false, /*url=*/GURL(""), - /*ukm_service=*/nullptr); + /*ukm_recorder=*/nullptr); - logger.RecordJourneyStatsHistograms( - JourneyLogger::COMPLETION_STATUS_COMPLETED); + logger.SetCompleted(); histogram_tester.ExpectBucketCount("PaymentRequest.CanMakePayment.Usage", JourneyLogger::CAN_MAKE_PAYMENT_NOT_USED, @@ -60,7 +44,7 @@ TEST(JourneyLoggerTest, RecordJourneyStatsHistograms_CanMakePaymentNotCalled_ShowAndUserAbort) { base::HistogramTester histogram_tester; JourneyLogger logger(/*is_incognito=*/false, /*url=*/GURL(""), - /*ukm_service=*/nullptr); + /*ukm_recorder=*/nullptr); // Expect no log for CanMakePayment. EXPECT_THAT( @@ -70,8 +54,7 @@ TEST(JourneyLoggerTest, // The merchant does not query CanMakePayment, show the PaymentRequest and the // user aborts it. logger.SetShowCalled(); - logger.RecordJourneyStatsHistograms( - JourneyLogger::COMPLETION_STATUS_USER_ABORTED); + logger.SetAborted(JourneyLogger::ABORT_REASON_ABORTED_BY_USER); histogram_tester.ExpectBucketCount("PaymentRequest.CanMakePayment.Usage", JourneyLogger::CAN_MAKE_PAYMENT_NOT_USED, @@ -90,7 +73,7 @@ TEST(JourneyLoggerTest, RecordJourneyStatsHistograms_CanMakePaymentNotCalled_ShowAndOtherAbort) { base::HistogramTester histogram_tester; JourneyLogger logger(/*is_incognito=*/false, /*url=*/GURL(""), - /*ukm_service=*/nullptr); + /*ukm_recorder=*/nullptr); // Expect no log for CanMakePayment. EXPECT_THAT( @@ -100,8 +83,7 @@ TEST(JourneyLoggerTest, // The merchant does not query CanMakePayment, show the PaymentRequest and // there is an abort not initiated by the user. logger.SetShowCalled(); - logger.RecordJourneyStatsHistograms( - JourneyLogger::COMPLETION_STATUS_OTHER_ABORTED); + logger.SetAborted(JourneyLogger::ABORT_REASON_OTHER); histogram_tester.ExpectBucketCount("PaymentRequest.CanMakePayment.Usage", JourneyLogger::CAN_MAKE_PAYMENT_NOT_USED, @@ -120,7 +102,7 @@ TEST(JourneyLoggerTest, RecordJourneyStatsHistograms_CanMakePaymentNotCalled_ShowAndComplete) { base::HistogramTester histogram_tester; JourneyLogger logger(/*is_incognito=*/false, /*url=*/GURL(""), - /*ukm_service=*/nullptr); + /*ukm_recorder=*/nullptr); // Expect no log for CanMakePayment. EXPECT_THAT( @@ -130,8 +112,7 @@ TEST(JourneyLoggerTest, // The merchant does not query CanMakePayment, show the PaymentRequest and the // user completes it. logger.SetShowCalled(); - logger.RecordJourneyStatsHistograms( - JourneyLogger::COMPLETION_STATUS_COMPLETED); + logger.SetCompleted(); histogram_tester.ExpectBucketCount("PaymentRequest.CanMakePayment.Usage", JourneyLogger::CAN_MAKE_PAYMENT_NOT_USED, @@ -150,7 +131,7 @@ TEST(JourneyLoggerTest, RecordJourneyStatsHistograms_CanMakePaymentCalled_FalseAndNoShow) { base::HistogramTester histogram_tester; JourneyLogger logger(/*is_incognito=*/false, /*url=*/GURL(""), - /*ukm_service=*/nullptr); + /*ukm_recorder=*/nullptr); // Expect no log for CanMakePayment. EXPECT_THAT( @@ -159,8 +140,7 @@ TEST(JourneyLoggerTest, // The user cannot make payment and the PaymentRequest is not shown. logger.SetCanMakePaymentValue(false); - logger.RecordJourneyStatsHistograms( - JourneyLogger::COMPLETION_STATUS_OTHER_ABORTED); + logger.SetAborted(JourneyLogger::ABORT_REASON_OTHER); histogram_tester.ExpectBucketCount("PaymentRequest.CanMakePayment.Usage", JourneyLogger::CAN_MAKE_PAYMENT_USED, 1); @@ -169,7 +149,8 @@ TEST(JourneyLoggerTest, // shown. histogram_tester.ExpectBucketCount( "PaymentRequest.CanMakePayment.Used.EffectOnShow", - JourneyLogger::CMP_SHOW_COULD_NOT_MAKE_PAYMENT_AND_DID_NOT_SHOW, 1); + JourneyLogger::CMP_EFFECT_ON_SHOW_COULD_NOT_MAKE_PAYMENT_AND_DID_NOT_SHOW, + 1); // There should be no completion stats since PR was not shown to the user. EXPECT_THAT( @@ -184,7 +165,7 @@ TEST(JourneyLoggerTest, RecordJourneyStatsHistograms_CanMakePaymentCalled_TrueAndNoShow) { base::HistogramTester histogram_tester; JourneyLogger logger(/*is_incognito=*/false, /*url=*/GURL(""), - /*ukm_service=*/nullptr); + /*ukm_recorder=*/nullptr); // Expect no log for CanMakePayment. EXPECT_THAT( @@ -193,8 +174,7 @@ TEST(JourneyLoggerTest, // The user cannot make payment and the PaymentRequest is not shown. logger.SetCanMakePaymentValue(true); - logger.RecordJourneyStatsHistograms( - JourneyLogger::COMPLETION_STATUS_OTHER_ABORTED); + logger.SetAborted(JourneyLogger::ABORT_REASON_OTHER); histogram_tester.ExpectBucketCount("PaymentRequest.CanMakePayment.Usage", JourneyLogger::CAN_MAKE_PAYMENT_USED, 1); @@ -203,7 +183,7 @@ TEST(JourneyLoggerTest, // shown. histogram_tester.ExpectBucketCount( "PaymentRequest.CanMakePayment.Used.EffectOnShow", - JourneyLogger::CMP_SHOW_COULD_MAKE_PAYMENT, 1); + JourneyLogger::CMP_EFFECT_ON_SHOW_COULD_MAKE_PAYMENT, 1); // There should be no completion stats since PR was not shown to the user. EXPECT_THAT( @@ -218,7 +198,7 @@ TEST(JourneyLoggerTest, RecordJourneyStatsHistograms_CanMakePaymentCalled_FalseShowAndUserAbort) { base::HistogramTester histogram_tester; JourneyLogger logger(/*is_incognito=*/false, /*url=*/GURL(""), - /*ukm_service=*/nullptr); + /*ukm_recorder=*/nullptr); // Expect no log for CanMakePayment. EXPECT_THAT( @@ -228,8 +208,7 @@ TEST(JourneyLoggerTest, // The user cannot make payment and the PaymentRequest is not shown. logger.SetShowCalled(); logger.SetCanMakePaymentValue(false); - logger.RecordJourneyStatsHistograms( - JourneyLogger::COMPLETION_STATUS_USER_ABORTED); + logger.SetAborted(JourneyLogger::ABORT_REASON_ABORTED_BY_USER); histogram_tester.ExpectBucketCount("PaymentRequest.CanMakePayment.Usage", JourneyLogger::CAN_MAKE_PAYMENT_USED, 1); @@ -238,7 +217,7 @@ TEST(JourneyLoggerTest, // shown. histogram_tester.ExpectBucketCount( "PaymentRequest.CanMakePayment.Used.EffectOnShow", - JourneyLogger::CMP_SHOW_DID_SHOW, 1); + JourneyLogger::CMP_EFFECT_ON_SHOW_DID_SHOW, 1); // There should be a record for an abort when CanMakePayment is false but the // PR is shown to the user. histogram_tester.ExpectBucketCount( @@ -252,7 +231,7 @@ TEST(JourneyLoggerTest, RecordJourneyStatsHistograms_CanMakePaymentCalled_FalseShowAndOtherAbort) { base::HistogramTester histogram_tester; JourneyLogger logger(/*is_incognito=*/false, /*url=*/GURL(""), - /*ukm_service=*/nullptr); + /*ukm_recorder=*/nullptr); // Expect no log for CanMakePayment. EXPECT_THAT( @@ -262,8 +241,7 @@ TEST(JourneyLoggerTest, // The user cannot make payment and the PaymentRequest is not shown. logger.SetShowCalled(); logger.SetCanMakePaymentValue(false); - logger.RecordJourneyStatsHistograms( - JourneyLogger::COMPLETION_STATUS_OTHER_ABORTED); + logger.SetAborted(JourneyLogger::ABORT_REASON_OTHER); histogram_tester.ExpectBucketCount("PaymentRequest.CanMakePayment.Usage", JourneyLogger::CAN_MAKE_PAYMENT_USED, 1); @@ -272,7 +250,7 @@ TEST(JourneyLoggerTest, // shown. histogram_tester.ExpectBucketCount( "PaymentRequest.CanMakePayment.Used.EffectOnShow", - JourneyLogger::CMP_SHOW_DID_SHOW, 1); + JourneyLogger::CMP_EFFECT_ON_SHOW_DID_SHOW, 1); // There should be a record for an abort when CanMakePayment is false but the // PR is shown to the user. histogram_tester.ExpectBucketCount( @@ -286,7 +264,7 @@ TEST(JourneyLoggerTest, RecordJourneyStatsHistograms_CanMakePaymentCalled_FalseShowAndComplete) { base::HistogramTester histogram_tester; JourneyLogger logger(/*is_incognito=*/false, /*url=*/GURL(""), - /*ukm_service=*/nullptr); + /*ukm_recorder=*/nullptr); // Expect no log for CanMakePayment. EXPECT_THAT( @@ -296,8 +274,7 @@ TEST(JourneyLoggerTest, // The user cannot make payment and the PaymentRequest is not shown. logger.SetShowCalled(); logger.SetCanMakePaymentValue(false); - logger.RecordJourneyStatsHistograms( - JourneyLogger::COMPLETION_STATUS_COMPLETED); + logger.SetCompleted(); histogram_tester.ExpectBucketCount("PaymentRequest.CanMakePayment.Usage", JourneyLogger::CAN_MAKE_PAYMENT_USED, 1); @@ -306,7 +283,7 @@ TEST(JourneyLoggerTest, // shown. histogram_tester.ExpectBucketCount( "PaymentRequest.CanMakePayment.Used.EffectOnShow", - JourneyLogger::CMP_SHOW_DID_SHOW, 1); + JourneyLogger::CMP_EFFECT_ON_SHOW_DID_SHOW, 1); // There should be a record for an completion when CanMakePayment is false but // the PR is shown to the user. @@ -321,7 +298,7 @@ TEST(JourneyLoggerTest, RecordJourneyStatsHistograms_CanMakePaymentCalled_TrueShowAndUserAbort) { base::HistogramTester histogram_tester; JourneyLogger logger(/*is_incognito=*/false, /*url=*/GURL(""), - /*ukm_service=*/nullptr); + /*ukm_recorder=*/nullptr); // Expect no log for CanMakePayment. EXPECT_THAT( @@ -331,8 +308,7 @@ TEST(JourneyLoggerTest, // The user cannot make payment and the PaymentRequest is not shown. logger.SetShowCalled(); logger.SetCanMakePaymentValue(true); - logger.RecordJourneyStatsHistograms( - JourneyLogger::COMPLETION_STATUS_USER_ABORTED); + logger.SetAborted(JourneyLogger::ABORT_REASON_ABORTED_BY_USER); histogram_tester.ExpectBucketCount("PaymentRequest.CanMakePayment.Usage", JourneyLogger::CAN_MAKE_PAYMENT_USED, 1); @@ -341,8 +317,8 @@ TEST(JourneyLoggerTest, // shown. histogram_tester.ExpectBucketCount( "PaymentRequest.CanMakePayment.Used.EffectOnShow", - JourneyLogger::CMP_SHOW_DID_SHOW | - JourneyLogger::CMP_SHOW_COULD_MAKE_PAYMENT, + JourneyLogger::CMP_EFFECT_ON_SHOW_DID_SHOW | + JourneyLogger::CMP_EFFECT_ON_SHOW_COULD_MAKE_PAYMENT, 1); // There should be a record for an abort when CanMakePayment is true and the // PR is shown to the user. @@ -357,7 +333,7 @@ TEST(JourneyLoggerTest, RecordJourneyStatsHistograms_CanMakePaymentCalled_TrueShowAndOtherAbort) { base::HistogramTester histogram_tester; JourneyLogger logger(/*is_incognito=*/false, /*url=*/GURL(""), - /*ukm_service=*/nullptr); + /*ukm_recorder=*/nullptr); // Expect no log for CanMakePayment. EXPECT_THAT( @@ -367,8 +343,7 @@ TEST(JourneyLoggerTest, // The user cannot make payment and the PaymentRequest is not shown. logger.SetShowCalled(); logger.SetCanMakePaymentValue(true); - logger.RecordJourneyStatsHistograms( - JourneyLogger::COMPLETION_STATUS_OTHER_ABORTED); + logger.SetAborted(JourneyLogger::ABORT_REASON_OTHER); histogram_tester.ExpectBucketCount("PaymentRequest.CanMakePayment.Usage", JourneyLogger::CAN_MAKE_PAYMENT_USED, 1); @@ -377,8 +352,8 @@ TEST(JourneyLoggerTest, // shown. histogram_tester.ExpectBucketCount( "PaymentRequest.CanMakePayment.Used.EffectOnShow", - JourneyLogger::CMP_SHOW_DID_SHOW | - JourneyLogger::CMP_SHOW_COULD_MAKE_PAYMENT, + JourneyLogger::CMP_EFFECT_ON_SHOW_DID_SHOW | + JourneyLogger::CMP_EFFECT_ON_SHOW_COULD_MAKE_PAYMENT, 1); // There should be a record for an abort when CanMakePayment is true and the // PR is shown to the user. @@ -393,7 +368,7 @@ TEST(JourneyLoggerTest, RecordJourneyStatsHistograms_CanMakePaymentCalled_TrueShowAndComplete) { base::HistogramTester histogram_tester; JourneyLogger logger(/*is_incognito=*/false, /*url=*/GURL(""), - /*ukm_service=*/nullptr); + /*ukm_recorder=*/nullptr); // Expect no log for CanMakePayment. EXPECT_THAT( @@ -403,8 +378,7 @@ TEST(JourneyLoggerTest, // The user cannot make payment and the PaymentRequest is not shown. logger.SetShowCalled(); logger.SetCanMakePaymentValue(true); - logger.RecordJourneyStatsHistograms( - JourneyLogger::COMPLETION_STATUS_COMPLETED); + logger.SetCompleted(); histogram_tester.ExpectBucketCount("PaymentRequest.CanMakePayment.Usage", JourneyLogger::CAN_MAKE_PAYMENT_USED, 1); @@ -413,8 +387,8 @@ TEST(JourneyLoggerTest, // shown. histogram_tester.ExpectBucketCount( "PaymentRequest.CanMakePayment.Used.EffectOnShow", - JourneyLogger::CMP_SHOW_DID_SHOW | - JourneyLogger::CMP_SHOW_COULD_MAKE_PAYMENT, + JourneyLogger::CMP_EFFECT_ON_SHOW_DID_SHOW | + JourneyLogger::CMP_EFFECT_ON_SHOW_COULD_MAKE_PAYMENT, 1); // There should be a record for a completion when CanMakePayment is true and // the PR is shown to the user. @@ -429,7 +403,7 @@ TEST(JourneyLoggerTest, RecordJourneyStatsHistograms_CanMakePayment_IncognitoTab) { base::HistogramTester histogram_tester; JourneyLogger logger(/*is_incognito=*/true, /*url=*/GURL(""), - /*ukm_service=*/nullptr); + /*ukm_recorder=*/nullptr); // Expect no log for CanMakePayment. EXPECT_THAT( @@ -439,8 +413,7 @@ TEST(JourneyLoggerTest, // The user cannot make payment and the PaymentRequest is not shown. logger.SetShowCalled(); logger.SetCanMakePaymentValue(true); - logger.RecordJourneyStatsHistograms( - JourneyLogger::COMPLETION_STATUS_COMPLETED); + logger.SetCompleted(); // Expect no log for CanMakePayment. EXPECT_THAT( @@ -454,14 +427,13 @@ TEST(JourneyLoggerTest, RecordJourneyStatsHistograms_SuggestionsForEverything_Completed) { base::HistogramTester histogram_tester; JourneyLogger logger(/*is_incognito=*/false, /*url=*/GURL(""), - /*ukm_service=*/nullptr); + /*ukm_recorder=*/nullptr); // Simulate that the user had suggestions for all the requested sections. logger.SetNumberOfSuggestionsShown(JourneyLogger::SECTION_CREDIT_CARDS, 1); // Simulate that the user completes the checkout. - logger.RecordJourneyStatsHistograms( - JourneyLogger::COMPLETION_STATUS_COMPLETED); + logger.SetCompleted(); histogram_tester.ExpectBucketCount( "PaymentRequest.UserHadSuggestionsForEverything.EffectOnCompletion", @@ -479,14 +451,13 @@ TEST(JourneyLoggerTest, RecordJourneyStatsHistograms_SuggestionsForEverything_UserAborted) { base::HistogramTester histogram_tester; JourneyLogger logger(/*is_incognito=*/false, /*url=*/GURL(""), - /*ukm_service=*/nullptr); + /*ukm_recorder=*/nullptr); // Simulate that the user had suggestions for all the requested sections. logger.SetNumberOfSuggestionsShown(JourneyLogger::SECTION_CREDIT_CARDS, 1); // Simulate that the user aborts the checkout. - logger.RecordJourneyStatsHistograms( - JourneyLogger::COMPLETION_STATUS_USER_ABORTED); + logger.SetAborted(JourneyLogger::ABORT_REASON_ABORTED_BY_USER); histogram_tester.ExpectBucketCount( "PaymentRequest.UserHadSuggestionsForEverything.EffectOnCompletion", @@ -504,14 +475,13 @@ TEST(JourneyLoggerTest, RecordJourneyStatsHistograms_SuggestionsForEverything_OtherAborted) { base::HistogramTester histogram_tester; JourneyLogger logger(/*is_incognito=*/false, /*url=*/GURL(""), - /*ukm_service=*/nullptr); + /*ukm_recorder=*/nullptr); // Simulate that the user had suggestions for all the requested sections. logger.SetNumberOfSuggestionsShown(JourneyLogger::SECTION_CREDIT_CARDS, 1); // Simulate that the checkout is aborted. - logger.RecordJourneyStatsHistograms( - JourneyLogger::COMPLETION_STATUS_OTHER_ABORTED); + logger.SetAborted(JourneyLogger::ABORT_REASON_OTHER); histogram_tester.ExpectBucketCount( "PaymentRequest.UserHadSuggestionsForEverything.EffectOnCompletion", @@ -530,14 +500,13 @@ TEST(JourneyLoggerTest, RecordJourneyStatsHistograms_SuggestionsForEverything_Incognito) { base::HistogramTester histogram_tester; JourneyLogger logger(/*is_incognito=*/true, /*url=*/GURL(""), - /*ukm_service=*/nullptr); + /*ukm_recorder=*/nullptr); // Simulate that the user had suggestions for all the requested sections. logger.SetNumberOfSuggestionsShown(JourneyLogger::SECTION_CREDIT_CARDS, 1); // Simulate that the user completes the checkout. - logger.RecordJourneyStatsHistograms( - JourneyLogger::COMPLETION_STATUS_COMPLETED); + logger.SetCompleted(); histogram_tester.ExpectBucketCount( "PaymentRequest.UserHadSuggestionsForEverything.EffectOnCompletion", @@ -555,14 +524,13 @@ TEST(JourneyLoggerTest, RecordJourneyStatsHistograms_NoSuggestionsForEverything_Completed) { base::HistogramTester histogram_tester; JourneyLogger logger(/*is_incognito=*/false, /*url=*/GURL(""), - /*ukm_service=*/nullptr); + /*ukm_recorder=*/nullptr); // Simulate that the user had suggestions for all the requested sections. logger.SetNumberOfSuggestionsShown(JourneyLogger::SECTION_CREDIT_CARDS, 0); // Simulate that the user completes the checkout. - logger.RecordJourneyStatsHistograms( - JourneyLogger::COMPLETION_STATUS_COMPLETED); + logger.SetCompleted(); histogram_tester.ExpectBucketCount( "PaymentRequest.UserDidNotHaveSuggestionsForEverything." @@ -581,14 +549,13 @@ TEST(JourneyLoggerTest, RecordJourneyStatsHistograms_NoSuggestionsForEverything_UserAborted) { base::HistogramTester histogram_tester; JourneyLogger logger(/*is_incognito=*/false, /*url=*/GURL(""), - /*ukm_service=*/nullptr); + /*ukm_recorder=*/nullptr); // Simulate that the user had suggestions for all the requested sections. logger.SetNumberOfSuggestionsShown(JourneyLogger::SECTION_CREDIT_CARDS, 0); // Simulate that the user aborts the checkout. - logger.RecordJourneyStatsHistograms( - JourneyLogger::COMPLETION_STATUS_USER_ABORTED); + logger.SetAborted(JourneyLogger::ABORT_REASON_ABORTED_BY_USER); histogram_tester.ExpectBucketCount( "PaymentRequest.UserDidNotHaveSuggestionsForEverything." @@ -607,14 +574,13 @@ TEST(JourneyLoggerTest, RecordJourneyStatsHistograms_NoSuggestionsForEverything_OtherAborted) { base::HistogramTester histogram_tester; JourneyLogger logger(/*is_incognito=*/false, /*url=*/GURL(""), - /*ukm_service=*/nullptr); + /*ukm_recorder=*/nullptr); // Simulate that the user had suggestions for all the requested sections. logger.SetNumberOfSuggestionsShown(JourneyLogger::SECTION_CREDIT_CARDS, 0); - // Simulate that the user aborts the checkout. - logger.RecordJourneyStatsHistograms( - JourneyLogger::COMPLETION_STATUS_OTHER_ABORTED); + // Simulate that the the checkout is aborted. + logger.SetAborted(JourneyLogger::ABORT_REASON_OTHER); histogram_tester.ExpectBucketCount( "PaymentRequest.UserDidNotHaveSuggestionsForEverything." @@ -634,14 +600,13 @@ TEST(JourneyLoggerTest, RecordJourneyStatsHistograms_NoSuggestionsForEverything_Incognito) { base::HistogramTester histogram_tester; JourneyLogger logger(/*is_incognito=*/true, /*url=*/GURL(""), - /*ukm_service=*/nullptr); + /*ukm_recorder=*/nullptr); // Simulate that the user had suggestions for all the requested sections. logger.SetNumberOfSuggestionsShown(JourneyLogger::SECTION_CREDIT_CARDS, 0); // Simulate that the user aborts the checkout. - logger.RecordJourneyStatsHistograms( - JourneyLogger::COMPLETION_STATUS_USER_ABORTED); + logger.SetAborted(JourneyLogger::ABORT_REASON_ABORTED_BY_USER); histogram_tester.ExpectBucketCount( "PaymentRequest.UserDidNotHaveSuggestionsForEverything." @@ -659,9 +624,9 @@ TEST(JourneyLoggerTest, TEST(JourneyLoggerTest, RecordJourneyStatsHistograms_TwoPaymentRequests) { base::HistogramTester histogram_tester; JourneyLogger logger1(/*is_incognito=*/false, /*url=*/GURL(""), - /*ukm_service=*/nullptr); + /*ukm_recorder=*/nullptr); JourneyLogger logger2(/*is_incognito=*/false, /*url=*/GURL(""), - /*ukm_service=*/nullptr); + /*ukm_recorder=*/nullptr); // Make the two loggers have different data. logger1.SetShowCalled(); @@ -673,10 +638,8 @@ TEST(JourneyLoggerTest, RecordJourneyStatsHistograms_TwoPaymentRequests) { logger2.SetNumberOfSuggestionsShown(JourneyLogger::SECTION_CREDIT_CARDS, 0); // Simulate that the user completes one checkout and aborts the other. - logger1.RecordJourneyStatsHistograms( - JourneyLogger::COMPLETION_STATUS_COMPLETED); - logger2.RecordJourneyStatsHistograms( - JourneyLogger::COMPLETION_STATUS_USER_ABORTED); + logger1.SetCompleted(); + logger2.SetAborted(JourneyLogger::ABORT_REASON_ABORTED_BY_USER); // Make sure the appropriate metrics were logged for logger1. histogram_tester.ExpectBucketCount( @@ -703,51 +666,41 @@ TEST(JourneyLoggerTest, RecordJourneyStatsHistograms_TwoPaymentRequests) { // Tests that the Payment Request UKMs are logged correctly. TEST(JourneyLoggerTest, RecordJourneyStatsHistograms_CheckoutFunnelUkm) { - base::test::ScopedFeatureList scoped_feature_list_; - scoped_feature_list_.InitAndEnableFeature(autofill::kAutofillUkmLogging); - - ukm::UkmServiceTestingHarness ukm_service_test_harness; - ukm::TestUkmService* ukm_service = - ukm_service_test_harness.test_ukm_service(); + ukm::TestUkmRecorder ukm_recorder; char test_url[] = "http://www.google.com/"; base::HistogramTester histogram_tester; JourneyLogger logger(/*is_incognito=*/true, /*url=*/GURL(test_url), - /*ukm_service=*/ukm_service); + /*ukm_recorder=*/&ukm_recorder); // Simulate that the user aborts after being shown the Payment Request and // clicking pay. logger.SetEventOccurred(JourneyLogger::EVENT_SHOWN); logger.SetEventOccurred(JourneyLogger::EVENT_PAY_CLICKED); - logger.RecordJourneyStatsHistograms( - JourneyLogger::COMPLETION_STATUS_USER_ABORTED); + logger.SetAborted(JourneyLogger::ABORT_REASON_ABORTED_BY_USER); // Make sure the UKM was logged correctly. - ASSERT_EQ(1U, ukm_service->sources_count()); - const ukm::UkmSource* source = ukm_service->GetSourceForUrl(test_url); + ASSERT_EQ(1U, ukm_recorder.sources_count()); + const ukm::UkmSource* source = ukm_recorder.GetSourceForUrl(test_url); ASSERT_NE(nullptr, source); - ASSERT_EQ(1U, ukm_service->entries_count()); - const ukm::UkmEntry* entry = ukm_service->GetEntry(0); - EXPECT_EQ(source->id(), entry->source_id()); - - ukm::Entry entry_proto; - entry->PopulateProto(&entry_proto); - EXPECT_EQ(source->id(), entry_proto.source_id()); + ASSERT_EQ(1U, ukm_recorder.entries_count()); + const ukm::mojom::UkmEntry* entry = ukm_recorder.GetEntry(0); + EXPECT_EQ(source->id(), entry->source_id); EXPECT_EQ(base::HashMetricName(internal::kUKMCheckoutEventsEntryName), - entry_proto.event_hash()); + entry->event_hash); - const ukm::Entry_Metric* status_metric = FindMetric( - internal::kUKMCompletionStatusMetricName, entry_proto.metrics()); + const ukm::mojom::UkmMetric* status_metric = ukm::TestUkmRecorder::FindMetric( + entry, internal::kUKMCompletionStatusMetricName); ASSERT_NE(nullptr, status_metric); EXPECT_EQ(JourneyLogger::COMPLETION_STATUS_USER_ABORTED, - status_metric->value()); + status_metric->value); - const ukm::Entry_Metric* step_metric = - FindMetric(internal::kUKMEventsMetricName, entry_proto.metrics()); + const ukm::mojom::UkmMetric* step_metric = + ukm::TestUkmRecorder::FindMetric(entry, internal::kUKMEventsMetricName); ASSERT_NE(nullptr, step_metric); EXPECT_EQ(JourneyLogger::EVENT_SHOWN | JourneyLogger::EVENT_PAY_CLICKED, - step_metric->value()); + step_metric->value); } -} // namespace payments
\ No newline at end of file +} // namespace payments diff --git a/chromium/components/payments/core/payment_instrument.cc b/chromium/components/payments/core/payment_instrument.cc index 097917c8427..7f2c8a0f0e8 100644 --- a/chromium/components/payments/core/payment_instrument.cc +++ b/chromium/components/payments/core/payment_instrument.cc @@ -7,13 +7,9 @@ namespace payments { PaymentInstrument::PaymentInstrument(const std::string& method_name, - const base::string16& label, - const base::string16& sublabel, int icon_resource_id, Type type) : method_name_(method_name), - label_(label), - sublabel_(sublabel), icon_resource_id_(icon_resource_id), type_(type) {} diff --git a/chromium/components/payments/core/payment_instrument.h b/chromium/components/payments/core/payment_instrument.h index 5d70847f4f2..6da07693d8e 100644 --- a/chromium/components/payments/core/payment_instrument.h +++ b/chromium/components/payments/core/payment_instrument.h @@ -39,27 +39,29 @@ class PaymentInstrument { // Returns whether the instrument is complete to be used as a payment method // without further editing. virtual bool IsCompleteForPayment() = 0; + // Returns a message to indicate to the user what's missing for the instrument + // to be complete for payment. + virtual base::string16 GetMissingInfoLabel() = 0; // Returns whether the instrument is valid for the purposes of responding to // canMakePayment. virtual bool IsValidForCanMakePayment() = 0; + // Records the use of this payment instrument. + virtual void RecordUse() = 0; + // Return the sub/label of payment instrument, to be displayed to the user. + virtual base::string16 GetLabel() const = 0; + virtual base::string16 GetSublabel() const = 0; const std::string& method_name() const { return method_name_; } - const base::string16& label() const { return label_; } - const base::string16& sublabel() const { return sublabel_; } int icon_resource_id() const { return icon_resource_id_; } Type type() { return type_; } protected: PaymentInstrument(const std::string& method_name, - const base::string16& label, - const base::string16& sublabel, int icon_resource_id, Type type); private: const std::string method_name_; - const base::string16 label_; - const base::string16 sublabel_; int icon_resource_id_; Type type_; diff --git a/chromium/components/payments/core/payment_prefs.cc b/chromium/components/payments/core/payment_prefs.cc new file mode 100644 index 00000000000..854769dad25 --- /dev/null +++ b/chromium/components/payments/core/payment_prefs.cc @@ -0,0 +1,18 @@ +// Copyright 2017 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/payments/core/payment_prefs.h" + +#include "components/pref_registry/pref_registry_syncable.h" + +namespace payments { + +const char kPaymentsFirstTransactionCompleted[] = + "payments.first_transaction_completed"; + +void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) { + registry->RegisterBooleanPref(kPaymentsFirstTransactionCompleted, false); +} + +} // namespace payments diff --git a/chromium/components/payments/core/payment_prefs.h b/chromium/components/payments/core/payment_prefs.h new file mode 100644 index 00000000000..1d5917ff2ac --- /dev/null +++ b/chromium/components/payments/core/payment_prefs.h @@ -0,0 +1,22 @@ +// Copyright 2017 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_PAYMENTS_CORE_PAYMENT_PREFS_H_ +#define COMPONENTS_PAYMENTS_CORE_PAYMENT_PREFS_H_ + +namespace user_prefs { +class PrefRegistrySyncable; +} + +namespace payments { + +// True if the profile has already successfully completed at least one payment +// request transaction. +extern const char kPaymentsFirstTransactionCompleted[]; + +void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry); + +} // namespace payments + +#endif // COMPONENTS_PAYMENTS_CORE_PAYMENT_PREFS_H_ diff --git a/chromium/components/payments/core/payment_request_data_util.cc b/chromium/components/payments/core/payment_request_data_util.cc index 8fb65723122..7f689419603 100644 --- a/chromium/components/payments/core/payment_request_data_util.cc +++ b/chromium/components/payments/core/payment_request_data_util.cc @@ -7,10 +7,13 @@ #include "base/strings/string16.h" #include "base/strings/string_split.h" #include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill_country.h" +#include "components/autofill/core/browser/autofill_data_util.h" #include "components/autofill/core/browser/autofill_profile.h" #include "components/autofill/core/browser/credit_card.h" #include "components/autofill/core/browser/field_types.h" #include "components/autofill/core/browser/personal_data_manager.h" +#include "components/autofill/core/browser/validation.h" #include "components/payments/core/basic_card_response.h" #include "components/payments/core/payment_address.h" #include "components/payments/core/payment_method_data.h" @@ -71,7 +74,7 @@ PaymentAddress GetPaymentAddressFromAutofillProfile( BasicCardResponse GetBasicCardResponseFromAutofillCreditCard( const autofill::CreditCard& card, const base::string16& cvc, - const std::vector<autofill::AutofillProfile*>& billing_profiles, + const autofill::AutofillProfile& billing_profile, const std::string& app_locale) { BasicCardResponse response; response.cardholder_name = card.GetRawInfo(autofill::CREDIT_CARD_NAME_FULL); @@ -81,79 +84,99 @@ BasicCardResponse GetBasicCardResponseFromAutofillCreditCard( card.GetRawInfo(autofill::CREDIT_CARD_EXP_4_DIGIT_YEAR); response.card_security_code = cvc; - // TODO(crbug.com/602666): Ensure we reach here only if the card has a billing - // address. Then add DCHECK(!card->billing_address_id().empty()). - if (!card.billing_address_id().empty()) { - const autofill::AutofillProfile* billing_address = - autofill::PersonalDataManager::GetProfileFromProfilesByGUID( - card.billing_address_id(), billing_profiles); - DCHECK(billing_address); - response.billing_address = - GetPaymentAddressFromAutofillProfile(*billing_address, app_locale); - } + response.billing_address = + GetPaymentAddressFromAutofillProfile(billing_profile, app_locale); return response; } -bool ParseBasicCardSupportedNetworks( +void ParseBasicCardSupportedNetworks( const std::vector<PaymentMethodData>& method_data, std::vector<std::string>* out_supported_networks, std::set<std::string>* out_basic_card_specified_networks) { DCHECK(out_supported_networks->empty()); DCHECK(out_basic_card_specified_networks->empty()); - std::set<std::string> card_networks{"amex", "diners", "discover", - "jcb", "mastercard", "mir", - "unionpay", "visa"}; + const std::set<std::string> kBasicCardNetworks{ + "amex", "diners", "discover", "jcb", + "mastercard", "mir", "unionpay", "visa"}; + std::set<std::string> remaining_card_networks(kBasicCardNetworks); for (const PaymentMethodData& method_data_entry : method_data) { if (method_data_entry.supported_methods.empty()) - return false; + return; for (const std::string& method : method_data_entry.supported_methods) { if (method.empty()) continue; - // If a card network is specified right in "supportedMethods", add it. const char kBasicCardMethodName[] = "basic-card"; - auto card_it = card_networks.find(method); - if (card_it != card_networks.end()) { + // If a card network is specified right in "supportedMethods", add it. + auto card_it = remaining_card_networks.find(method); + if (card_it != remaining_card_networks.end()) { out_supported_networks->push_back(method); - // |method| removed from |card_networks| so that it is not doubly added - // to |supported_card_networks_| if "basic-card" is specified with no - // supported networks. - card_networks.erase(card_it); + // |method| removed from |remaining_card_networks| so that it is not + // doubly added to |out_supported_networks|. + remaining_card_networks.erase(card_it); } else if (method == kBasicCardMethodName) { // For the "basic-card" method, check "supportedNetworks". if (method_data_entry.supported_networks.empty()) { // Empty |supported_networks| means all networks are supported. out_supported_networks->insert(out_supported_networks->end(), - card_networks.begin(), - card_networks.end()); - out_basic_card_specified_networks->insert(card_networks.begin(), - card_networks.end()); + remaining_card_networks.begin(), + remaining_card_networks.end()); + out_basic_card_specified_networks->insert(kBasicCardNetworks.begin(), + kBasicCardNetworks.end()); // Clear the set so that no further networks are added to // |out_supported_networks|. - card_networks.clear(); + remaining_card_networks.clear(); } else { // The merchant has specified a few basic card supported networks. Use // the mapping to transform to known basic-card types. for (const std::string& supported_network : method_data_entry.supported_networks) { // Make sure that the network was not already added to - // |out_supported_networks|. If it's still in |card_networks| it's - // fair game. - auto it = card_networks.find(supported_network); - if (it != card_networks.end()) { + // |out_supported_networks|. If it's still in + // |remaining_card_networks| it's fair game. + auto it = remaining_card_networks.find(supported_network); + if (it != remaining_card_networks.end()) { out_supported_networks->push_back(supported_network); + remaining_card_networks.erase(it); + } + if (kBasicCardNetworks.find(supported_network) != + kBasicCardNetworks.end()) { out_basic_card_specified_networks->insert(supported_network); - card_networks.erase(it); } } } } } } - return true; +} + +base::string16 GetFormattedPhoneNumberForDisplay( + const autofill::AutofillProfile& profile, + const std::string& locale) { + // Since the "+" is removed for some country's phone numbers, try to add a "+" + // and see if it is a valid phone number for a country. + // Having two "+" in front of a number has no effect on the formatted number. + // The reason for this is international phone numbers for another country. For + // example, without adding a "+", the US number 1-415-123-1234 for an AU + // address would be wrongly formatted as +61 1-415-123-1234 which is invalid. + std::string phone = base::UTF16ToUTF8(profile.GetInfo( + autofill::AutofillType(autofill::PHONE_HOME_WHOLE_NUMBER), locale)); + std::string tentative_intl_phone = "+" + phone; + + // Always favor the tentative international phone number if it's determined as + // being a valid number. + if (autofill::IsValidPhoneNumber( + base::UTF8ToUTF16(tentative_intl_phone), + GetCountryCodeWithFallback(&profile, locale))) { + return base::UTF8ToUTF16(FormatPhoneForDisplay( + tentative_intl_phone, GetCountryCodeWithFallback(&profile, locale))); + } + + return base::UTF8ToUTF16(FormatPhoneForDisplay( + phone, GetCountryCodeWithFallback(&profile, locale))); } std::string FormatPhoneForDisplay(const std::string& phone_number, @@ -168,5 +191,14 @@ std::string FormatPhoneForResponse(const std::string& phone_number, PhoneNumberUtil::PhoneNumberFormat::E164); } +std::string GetCountryCodeWithFallback(const autofill::AutofillProfile* profile, + const std::string& app_locale) { + std::string country_code = + base::UTF16ToUTF8(profile->GetRawInfo(autofill::ADDRESS_HOME_COUNTRY)); + if (!autofill::data_util::IsValidCountryCode(country_code)) + country_code = autofill::AutofillCountry::CountryCodeForLocale(app_locale); + return country_code; +} + } // namespace data_util } // namespace payments diff --git a/chromium/components/payments/core/payment_request_data_util.h b/chromium/components/payments/core/payment_request_data_util.h index f3120c8c3d6..c301946d5e5 100644 --- a/chromium/components/payments/core/payment_request_data_util.h +++ b/chromium/components/payments/core/payment_request_data_util.h @@ -35,7 +35,7 @@ PaymentAddress GetPaymentAddressFromAutofillProfile( BasicCardResponse GetBasicCardResponseFromAutofillCreditCard( const autofill::CreditCard& card, const base::string16& cvc, - const std::vector<autofill::AutofillProfile*>& billing_profiles, + const autofill::AutofillProfile& billing_profile, const std::string& app_locale); // Parse the supported card networks from supportedMethods and "basic-card"'s @@ -44,15 +44,19 @@ BasicCardResponse GetBasicCardResponseFromAutofillCreditCard( // |out_basic_card_supported_networks| is a subset of |out_supported_networks| // that includes all networks that were specified as part of "basic-card". This // is used to know whether to return the card network name (e.g., "visa") or -// "basic-card" in the PaymentResponse. Returns true on success, false on -// invalid data specified. |method_data.supported_networks| is expected to only -// contain basic-card card network names (the list is at +// "basic-card" in the PaymentResponse. |method_data.supported_networks| is +// expected to only contain basic-card card network names (the list is at // https://www.w3.org/Payments/card-network-ids). -bool ParseBasicCardSupportedNetworks( +void ParseBasicCardSupportedNetworks( const std::vector<PaymentMethodData>& method_data, std::vector<std::string>* out_supported_networks, std::set<std::string>* out_basic_card_supported_networks); +// Returns the phone number from the given |profile| formatted for display. +base::string16 GetFormattedPhoneNumberForDisplay( + const autofill::AutofillProfile& profile, + const std::string& locale); + // Formats the given number |phone_number| to // i18n::phonenumbers::PhoneNumberUtil::PhoneNumberFormat::INTERNATIONAL format // by using i18n::phonenumbers::PhoneNumberUtil::Format. @@ -67,6 +71,12 @@ std::string FormatPhoneForDisplay(const std::string& phone_number, std::string FormatPhoneForResponse(const std::string& phone_number, const std::string& country_code); +// Returns a country code to be used when validating this profile. If the +// profile has a valid country code set, it is returned. If not, a country code +// associated with |app_locale| is used as a fallback. +std::string GetCountryCodeWithFallback(const autofill::AutofillProfile* profile, + const std::string& app_locale); + } // namespace data_util } // namespace payments diff --git a/chromium/components/payments/core/payment_request_data_util_unittest.cc b/chromium/components/payments/core/payment_request_data_util_unittest.cc index 5ad7e109637..3181a47d282 100644 --- a/chromium/components/payments/core/payment_request_data_util_unittest.cc +++ b/chromium/components/payments/core/payment_request_data_util_unittest.cc @@ -7,6 +7,7 @@ #include <memory> #include "base/json/json_writer.h" +#include "base/macros.h" #include "base/strings/utf_string_conversions.h" #include "base/values.h" #include "components/autofill/core/browser/autofill_profile.h" @@ -49,8 +50,7 @@ TEST(PaymentRequestDataUtilTest, GetBasicCardResponseFromAutofillCreditCard) { card.set_billing_address_id(address.guid()); std::unique_ptr<base::DictionaryValue> response_value = payments::data_util::GetBasicCardResponseFromAutofillCreditCard( - card, base::ASCIIToUTF16("123"), - std::vector<autofill::AutofillProfile*>{&address}, "en-US") + card, base::ASCIIToUTF16("123"), address, "en-US") .ToDictionaryValue(); std::string json_response; base::JSONWriter::Write(*response_value, &json_response); @@ -91,5 +91,171 @@ TEST(PaymentRequestDataUtilTest, FormatPhoneForDisplay) { payments::data_util::FormatPhoneForDisplay("142685300", "FR")); } +// Test for the GetFormattedPhoneNumberForDisplay method. +struct PhoneNumberFormatCase { + PhoneNumberFormatCase(const char* phone, + const char* country, + const char* expected_format, + const char* locale = "") + : phone(phone), + country(country), + expected_format(expected_format), + locale(locale) {} + + const char* phone; + const char* country; + const char* expected_format; + const char* locale; +}; + +class GetFormattedPhoneNumberForDisplayTest + : public testing::TestWithParam<PhoneNumberFormatCase> {}; + +TEST_P(GetFormattedPhoneNumberForDisplayTest, + GetFormattedPhoneNumberForDisplay) { + autofill::AutofillProfile profile; + profile.SetRawInfo(autofill::PHONE_HOME_WHOLE_NUMBER, + base::UTF8ToUTF16(GetParam().phone)); + profile.SetRawInfo(autofill::ADDRESS_HOME_COUNTRY, + base::UTF8ToUTF16(GetParam().country)); + EXPECT_EQ(GetParam().expected_format, + base::UTF16ToUTF8( + GetFormattedPhoneNumberForDisplay(profile, GetParam().locale))); +} + +INSTANTIATE_TEST_CASE_P( + GetFormattedPhoneNumberForDisplay, + GetFormattedPhoneNumberForDisplayTest, + testing::Values( + ////////////////////////// + // US phone in US. + ////////////////////////// + // Formatted phone numbers. + PhoneNumberFormatCase("+1 415-555-5555", "US", "+1 415-555-5555"), + PhoneNumberFormatCase("1 415-555-5555", "US", "+1 415-555-5555"), + PhoneNumberFormatCase("415-555-5555", "US", "+1 415-555-5555"), + // Raw phone numbers. + PhoneNumberFormatCase("+14155555555", "US", "+1 415-555-5555"), + PhoneNumberFormatCase("14155555555", "US", "+1 415-555-5555"), + PhoneNumberFormatCase("4155555555", "US", "+1 415-555-5555"), + + ////////////////////////// + // US phone in CA. + ////////////////////////// + // Formatted phone numbers. + PhoneNumberFormatCase("+1 415-555-5555", "CA", "+1 415-555-5555"), + PhoneNumberFormatCase("1 415-555-5555", "CA", "+1 415-555-5555"), + PhoneNumberFormatCase("415-555-5555", "CA", "+1 415-555-5555"), + // Raw phone numbers. + PhoneNumberFormatCase("+14155555555", "CA", "+1 415-555-5555"), + PhoneNumberFormatCase("14155555555", "CA", "+1 415-555-5555"), + PhoneNumberFormatCase("4155555555", "CA", "+1 415-555-5555"), + + ////////////////////////// + // US phone in AU. + ////////////////////////// + // A US phone with the country code is correctly formatted as an US + // number. + PhoneNumberFormatCase("+1 415-555-5555", "AU", "+1 415-555-5555"), + PhoneNumberFormatCase("1 415-555-5555", "AU", "+1 415-555-5555"), + // Without a country code, the phone is formatted for the profile's + // country, even if it's invalid. + PhoneNumberFormatCase("415-555-5555", "AU", "+61 4155555555"), + + ////////////////////////// + // US phone in MX. + ////////////////////////// + // A US phone with the country code is correctly formatted as an US + // number. + PhoneNumberFormatCase("+1 415-555-5555", "MX", "+1 415-555-5555"), + // "+52 1 415 555 5555" is a valid number for Mexico, + PhoneNumberFormatCase("1 415-555-5555", "MX", "+52 1 415 555 5555"), + // Without a country code, the phone is formatted for the profile's + // country. + PhoneNumberFormatCase("415-555-5555", "MX", "+52 415 555 5555"), + + ////////////////////////// + // AU phone in AU. + ////////////////////////// + // Formatted phone numbers. + PhoneNumberFormatCase("+61 2 9374 4000", "AU", "+61 2 9374 4000"), + PhoneNumberFormatCase("61 2 9374 4000", "AU", "+61 2 9374 4000"), + PhoneNumberFormatCase("02 9374 4000", "AU", "+61 2 9374 4000"), + PhoneNumberFormatCase("2 9374 4000", "AU", "+61 2 9374 4000"), + // Raw phone numbers. + PhoneNumberFormatCase("+61293744000", "AU", "+61 2 9374 4000"), + PhoneNumberFormatCase("61293744000", "AU", "+61 2 9374 4000"), + PhoneNumberFormatCase("0293744000", "AU", "+61 2 9374 4000"), + PhoneNumberFormatCase("293744000", "AU", "+61 2 9374 4000"), + + ////////////////////////// + // AU phone in US. + ////////////////////////// + // An AU phone with the country code is correctly formatted as an AU + // number. + PhoneNumberFormatCase("+61 2 9374 4000", "US", "+61 2 9374 4000"), + PhoneNumberFormatCase("61 2 9374 4000", "US", "+61 2 9374 4000"), + // Without a country code, the phone is formatted for the profile's + // country. + // This local AU number fits the length of a US number, so it's + // formatted for US. + PhoneNumberFormatCase("02 9374 4000", "US", "+1 029-374-4000"), + // This local AU number is formatted as an US number, even if it's + // invlaid. + PhoneNumberFormatCase("2 9374 4000", "US", "+1 293744000"), + + ////////////////////////// + // MX phone in MX. + ////////////////////////// + // Formatted phone numbers. + PhoneNumberFormatCase("+52 55 5342 8400", "MX", "+52 55 5342 8400"), + PhoneNumberFormatCase("52 55 5342 8400", "MX", "+52 55 5342 8400"), + PhoneNumberFormatCase("55 5342 8400", "MX", "+52 55 5342 8400"), + // Raw phone numbers. + PhoneNumberFormatCase("+525553428400", "MX", "+52 55 5342 8400"), + PhoneNumberFormatCase("525553428400", "MX", "+52 55 5342 8400"), + PhoneNumberFormatCase("5553428400", "MX", "+52 55 5342 8400"), + + ////////////////////////// + // MX phone in US. + ////////////////////////// + // A MX phone with the country code is correctly formatted as a MX + // number. + PhoneNumberFormatCase("+52 55 5342 8400", "US", "+52 55 5342 8400"), + PhoneNumberFormatCase("52 55 5342 8400", "US", "+52 55 5342 8400"), + // This local MX number fits the length of a US number, so it's + // formatted for US. + PhoneNumberFormatCase("55 5342 8400", "US", "+1 555-342-8400"))); + +INSTANTIATE_TEST_CASE_P( + GetFormattedPhoneNumberForDisplay_EdgeCases, + GetFormattedPhoneNumberForDisplayTest, + testing::Values( + ////////////////////////// + // No country. + ////////////////////////// + // Fallback to locale if no country is set. + PhoneNumberFormatCase("52 55 5342 8400", + "", + "+52 55 5342 8400", + "es_MX"), + PhoneNumberFormatCase("55 5342 8400", "", "+52 55 5342 8400", "es_MX"), + PhoneNumberFormatCase("55 5342 8400", "", "+1 555-342-8400", "en_US"), + PhoneNumberFormatCase("61 2 9374 4000", "", "+61 2 9374 4000", "en_AU"), + PhoneNumberFormatCase("02 9374 4000", "", "+61 2 9374 4000", "en_AU"), + + ////////////////////////// + // No country or locale. + ////////////////////////// + // Format according to the country code. + PhoneNumberFormatCase("61 2 9374 4000", "", "+61 2 9374 4000"), + PhoneNumberFormatCase("52 55 5342 8400", "", "+52 55 5342 8400"), + PhoneNumberFormatCase("1 415 555 5555", "", "+1 415-555-5555"), + // If no country code is found, formats for US. + PhoneNumberFormatCase("02 9374 4000", "", "+1 029-374-4000"), + PhoneNumberFormatCase("2 9374 4000", "", "+1 293744000"), + PhoneNumberFormatCase("55 5342 8400", "", "+1 555-342-8400"), + PhoneNumberFormatCase("52 55 5342 8400", "", "+52 55 5342 8400"), + PhoneNumberFormatCase("415-555-5555", "", "+1 415-555-5555"))); } // namespace data_util } // namespace payments diff --git a/chromium/components/payments/core/payment_request_delegate.h b/chromium/components/payments/core/payment_request_delegate.h index aab278af6f2..53a44c70626 100644 --- a/chromium/components/payments/core/payment_request_delegate.h +++ b/chromium/components/payments/core/payment_request_delegate.h @@ -10,23 +10,24 @@ #include "base/memory/weak_ptr.h" #include "components/autofill/core/browser/payments/full_card_request.h" -#include "third_party/libaddressinput/src/cpp/include/libaddressinput/source.h" -#include "third_party/libaddressinput/src/cpp/include/libaddressinput/storage.h" -namespace i18n { -namespace addressinput { -class Storage; -class Source; -} // namespace addressinput -} // namespace i18n +class GURL; namespace autofill { class CreditCard; class PersonalDataManager; +class RegionDataLoader; } // namespace autofill +class PrefService; + +namespace ukm { +class UkmRecorder; +} // namespace ukm + namespace payments { +class AddressNormalizer; class PaymentRequest; class PaymentRequestDelegate { @@ -53,17 +54,36 @@ class PaymentRequestDelegate { // Returns whether the user is in Incognito mode. virtual bool IsIncognito() const = 0; + // Returns true if the SSL certificate is valid. Should be called only for + // cryptographic schemes. + virtual bool IsSslCertificateValid() = 0; + + // Returns the URL of the page that is currently being displayed. + virtual const GURL& GetLastCommittedURL() const = 0; + // Starts a FullCardRequest to unmask |credit_card|. virtual void DoFullCardRequest( const autofill::CreditCard& credit_card, base::WeakPtr<autofill::payments::FullCardRequest::ResultDelegate> result_delegate) = 0; - // Returns the source and storage for country/region data loads. - virtual std::unique_ptr<const ::i18n::addressinput::Source> - GetAddressInputSource() = 0; - virtual std::unique_ptr<::i18n::addressinput::Storage> - GetAddressInputStorage() = 0; + // Returns a pointer to the address normalizer to use for the duration of this + // Payment Request. + virtual AddressNormalizer* GetAddressNormalizer() = 0; + + // Creates a new region data loader that will self delete, or a test mock. + virtual autofill::RegionDataLoader* GetRegionDataLoader() = 0; + + // Returns a pointer to the UKM service. + virtual ukm::UkmRecorder* GetUkmRecorder() = 0; + + // Returns the user's signed-in email address, or empty string if not signed + // in. + virtual std::string GetAuthenticatedEmail() const = 0; + + // Gets the pref service for the browser context associated with this + // PaymentRequest. + virtual PrefService* GetPrefService() = 0; }; } // namespace payments diff --git a/chromium/components/payments/core/payments_profile_comparator.cc b/chromium/components/payments/core/payments_profile_comparator.cc new file mode 100644 index 00000000000..3190bacf2a9 --- /dev/null +++ b/chromium/components/payments/core/payments_profile_comparator.cc @@ -0,0 +1,286 @@ +// Copyright 2017 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/payments/core/payments_profile_comparator.h" + +#include <algorithm> +#include <memory> + +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/address_i18n.h" +#include "components/autofill/core/browser/autofill_country.h" +#include "components/autofill/core/browser/autofill_profile.h" +#include "components/autofill/core/browser/field_types.h" +#include "components/autofill/core/browser/validation.h" +#include "components/payments/core/payment_options_provider.h" +#include "components/payments/core/payment_request_data_util.h" +#include "components/strings/grit/components_strings.h" +#include "third_party/libaddressinput/chromium/addressinput_util.h" +#include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_data.h" +#include "ui/base/l10n/l10n_util.h" + +namespace payments { + +PaymentsProfileComparator::PaymentsProfileComparator( + const std::string& app_locale, + const PaymentOptionsProvider& options) + : autofill::AutofillProfileComparator(app_locale), options_(options) {} + +PaymentsProfileComparator::~PaymentsProfileComparator() {} + +PaymentsProfileComparator::ProfileFields +PaymentsProfileComparator::GetMissingProfileFields( + const autofill::AutofillProfile* profile) const { + if (!profile) + return kName | kPhone | kEmail | kAddress; + + if (!cache_.count(profile->guid())) { + cache_[profile->guid()] = ComputeMissingFields(*profile); + } else { + // Cache hit. In debug mode, recompute and check that invalidation has + // occurred where necessary. + DCHECK_EQ(cache_[profile->guid()], ComputeMissingFields(*profile)) + << "Profiles must be invalidated when their contents change."; + } + + return cache_[profile->guid()]; +} + +std::vector<autofill::AutofillProfile*> +PaymentsProfileComparator::FilterProfilesForContact( + const std::vector<autofill::AutofillProfile*>& profiles) const { + // We will be removing profiles, so we operate on a copy. + std::vector<autofill::AutofillProfile*> processed = profiles; + + // Stable sort, since profiles are expected to be passed in frecency order. + std::stable_sort( + processed.begin(), processed.end(), + std::bind(&PaymentsProfileComparator::IsContactMoreComplete, this, + std::placeholders::_1, std::placeholders::_2)); + + auto it = processed.begin(); + while (it != processed.end()) { + if (GetContactCompletenessScore(*it) == 0) { + // Since profiles are sorted by completeness, this and any further + // profiles can be discarded. + processed.erase(it, processed.end()); + break; + } + + // Attempt to find a matching element in the vector before the current. + // This is quadratic, but the number of elements is generally small + // (< 10), so a more complicated algorithm would be overkill. + if (std::find_if(processed.begin(), it, + [&](autofill::AutofillProfile* prior) { + return IsContactEqualOrSuperset(*prior, **it); + }) != it) { + // Remove the subset profile. |it| will point to the next element after + // erasure. + it = processed.erase(it); + } else { + it++; + } + } + + return processed; +} + +bool PaymentsProfileComparator::IsContactEqualOrSuperset( + const autofill::AutofillProfile& super, + const autofill::AutofillProfile& sub) const { + if (options_.request_payer_name()) { + if (sub.HasInfo(autofill::NAME_FULL) && + !super.HasInfo(autofill::NAME_FULL)) { + return false; + } + if (!HaveMergeableNames(super, sub)) + return false; + } + if (options_.request_payer_phone()) { + if (sub.HasInfo(autofill::PHONE_HOME_WHOLE_NUMBER) && + !super.HasInfo(autofill::PHONE_HOME_WHOLE_NUMBER)) { + return false; + } + if (!HaveMergeablePhoneNumbers(super, sub)) + return false; + } + if (options_.request_payer_email()) { + if (sub.HasInfo(autofill::EMAIL_ADDRESS) && + !super.HasInfo(autofill::EMAIL_ADDRESS)) { + return false; + } + if (!HaveMergeableEmailAddresses(super, sub)) + return false; + } + return true; +} + +int PaymentsProfileComparator::GetContactCompletenessScore( + const autofill::AutofillProfile* profile) const { + // Create a bitmask of the fields that are both present and required. + ProfileFields present = + ~GetMissingProfileFields(profile) & GetRequiredProfileFieldsForContact(); + + // Count how many are set. + return !!(present & kName) + !!(present & kPhone) + !!(present & kEmail); +} + +bool PaymentsProfileComparator::IsContactInfoComplete( + const autofill::AutofillProfile* profile) const { + // Mask the fields that are missing with those that are requried. If any bits + // are set (i.e., the result is nonzero), then contact info is incomplete. + return !(GetMissingProfileFields(profile) & + GetRequiredProfileFieldsForContact()); +} + +base::string16 PaymentsProfileComparator::GetStringForMissingContactFields( + const autofill::AutofillProfile& profile) const { + return GetStringForMissingFields(GetMissingProfileFields(&profile) & + GetRequiredProfileFieldsForContact()); +} + +std::vector<autofill::AutofillProfile*> +PaymentsProfileComparator::FilterProfilesForShipping( + const std::vector<autofill::AutofillProfile*>& profiles) const { + // Since we'll be changing the order/contents of the const input vector, + // we make a copy. + std::vector<autofill::AutofillProfile*> processed = profiles; + + std::stable_sort( + processed.begin(), processed.end(), + std::bind(&PaymentsProfileComparator::IsShippingMoreComplete, this, + std::placeholders::_1, std::placeholders::_2)); + + // TODO(crbug.com/722949): Remove profiles with no relevant information, or + // which are subsets of more-complete profiles. + + return processed; +} + +int PaymentsProfileComparator::GetShippingCompletenessScore( + const autofill::AutofillProfile* profile) const { + // Create a bitmask of the fields that are both present and required. + ProfileFields present = + ~GetMissingProfileFields(profile) & GetRequiredProfileFieldsForShipping(); + + // Count how many are set. The completeness of the address is weighted so as + // to dominate the other fields. + return !!(present & kName) + !!(present & kPhone) + + (10 * !!(present & kAddress)); +} + +bool PaymentsProfileComparator::IsShippingComplete( + const autofill::AutofillProfile* profile) const { + // Mask the fields that are missing with those that are requried. If any bits + // are set (i.e., the result is nonzero), then shipping is incomplete. + return !(GetMissingProfileFields(profile) & + GetRequiredProfileFieldsForShipping()); +} + +base::string16 PaymentsProfileComparator::GetStringForMissingShippingFields( + const autofill::AutofillProfile& profile) const { + return GetStringForMissingFields(GetMissingProfileFields(&profile) & + GetRequiredProfileFieldsForShipping()); +} + +void PaymentsProfileComparator::Invalidate( + const autofill::AutofillProfile& profile) { + cache_.erase(profile.guid()); +} + +PaymentsProfileComparator::ProfileFields +PaymentsProfileComparator::ComputeMissingFields( + const autofill::AutofillProfile& profile) const { + ProfileFields missing = 0; + + if (!profile.HasInfo(autofill::NAME_FULL)) + missing |= kName; + + // Determine the country code to use when validating the phone number. Use + // the profile's country if it has one, or the code for the app locale + // otherwise. Note that international format numbers will always work--this + // is just the region that will be used to check if the number is + // potentially in a local format. + std::string country = + data_util::GetCountryCodeWithFallback(&profile, app_locale()); + + base::string16 phone = profile.GetInfo( + autofill::AutofillType(autofill::PHONE_HOME_WHOLE_NUMBER), app_locale()); + base::string16 intl_phone = base::UTF8ToUTF16("+" + base::UTF16ToUTF8(phone)); + if (!(autofill::IsValidPhoneNumber(phone, country) || + autofill::IsValidPhoneNumber(intl_phone, country))) + missing |= kPhone; + + base::string16 email = profile.GetInfo( + autofill::AutofillType(autofill::EMAIL_ADDRESS), app_locale()); + if (!autofill::IsValidEmailAddress(email)) + missing |= kEmail; + + if (!AreRequiredAddressFieldsPresent(profile)) + missing |= kAddress; + + return missing; +} + +PaymentsProfileComparator::ProfileFields +PaymentsProfileComparator::GetRequiredProfileFieldsForContact() const { + ProfileFields required = 0; + if (options_.request_payer_name()) + required |= kName; + if (options_.request_payer_phone()) + required |= kPhone; + if (options_.request_payer_email()) + required |= kEmail; + return required; +} + +PaymentsProfileComparator::ProfileFields +PaymentsProfileComparator::GetRequiredProfileFieldsForShipping() const { + return options_.request_shipping() ? (kAddress | kName | kPhone) : 0; +} + +base::string16 PaymentsProfileComparator::GetStringForMissingFields( + PaymentsProfileComparator::ProfileFields fields) const { + switch (fields) { + case 0: + // No bits are set, so no fields are missing. + return base::string16(); + case kName: + return l10n_util::GetStringUTF16(IDS_PAYMENTS_NAME_REQUIRED); + case kPhone: + return l10n_util::GetStringUTF16(IDS_PAYMENTS_PHONE_NUMBER_REQUIRED); + case kEmail: + return l10n_util::GetStringUTF16(IDS_PAYMENTS_EMAIL_REQUIRED); + case kAddress: + return l10n_util::GetStringUTF16(IDS_PAYMENTS_INVALID_ADDRESS); + default: + // Either multiple bits are set (likely) or one bit that doesn't + // correspond to a named constant is set (shouldn't happen). Return a + // generic "More information" message. + return l10n_util::GetStringUTF16(IDS_PAYMENTS_MORE_INFORMATION_REQUIRED); + } +} + +bool PaymentsProfileComparator::AreRequiredAddressFieldsPresent( + const autofill::AutofillProfile& profile) const { + std::unique_ptr<::i18n::addressinput::AddressData> data = + autofill::i18n::CreateAddressDataFromAutofillProfile(profile, + app_locale()); + + return autofill::addressinput::HasAllRequiredFields(*data); +} + +bool PaymentsProfileComparator::IsContactMoreComplete( + const autofill::AutofillProfile* p1, + const autofill::AutofillProfile* p2) const { + return GetContactCompletenessScore(p1) > GetContactCompletenessScore(p2); +} + +bool PaymentsProfileComparator::IsShippingMoreComplete( + const autofill::AutofillProfile* p1, + const autofill::AutofillProfile* p2) const { + return GetShippingCompletenessScore(p1) > GetShippingCompletenessScore(p2); +} + +} // namespace payments diff --git a/chromium/components/payments/core/payments_profile_comparator.h b/chromium/components/payments/core/payments_profile_comparator.h new file mode 100644 index 00000000000..e186e05efad --- /dev/null +++ b/chromium/components/payments/core/payments_profile_comparator.h @@ -0,0 +1,124 @@ +// Copyright 2017 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_PAYMENTS_CORE_PAYMENTS_PROFILE_COMPARATOR_H_ +#define COMPONENTS_PAYMENTS_CORE_PAYMENTS_PROFILE_COMPARATOR_H_ + +#include <map> +#include <string> +#include <vector> + +#include "components/autofill/core/browser/autofill_profile_comparator.h" + +// Utility functions used for processing and filtering address profiles +// (AutofillProfile). + +namespace autofill { +class AutofillProfile; +} // namespace autofill + +namespace payments { + +class PaymentOptionsProvider; + +// Helper class which evaluates profiles for similarity and completeness. +// Profiles are evaluated once for completeness, and the result is cached, +// meaning one instance of this class should be used per-request to avoid +// redoing expensive validation checks. +// Note that, if a profile is modified and saved during the course of the +// PaymentRequest, it is important to call the Invalidate method to ensure +// it is properly evaluated. +class PaymentsProfileComparator : public autofill::AutofillProfileComparator { + public: + // Bitmask of potentially-required fields used in evaluating completeness. + using ProfileFields = uint32_t; + const static ProfileFields kName = 1 << 0; + const static ProfileFields kPhone = 1 << 1; + const static ProfileFields kEmail = 1 << 2; + const static ProfileFields kAddress = 1 << 3; + + PaymentsProfileComparator(const std::string& app_locale, + const PaymentOptionsProvider& options); + ~PaymentsProfileComparator(); + + // Returns a bitmask indicating which fields (or groups of fields) on this + // profile are not complete and valid. + ProfileFields GetMissingProfileFields( + const autofill::AutofillProfile* profile) const; + + // Returns profiles for contact info, ordered by completeness and + // deduplicated. |profiles| should be passed in order of frecency, and this + // order will be preserved among equally-complete profiles. Deduplication here + // means that profiles returned are excluded if they are a subset of a more + // complete or more frecent profile. Completeness here refers only to the + // presence of the fields requested per the request_payer_* fields in + // |options|. + std::vector<autofill::AutofillProfile*> FilterProfilesForContact( + const std::vector<autofill::AutofillProfile*>& profiles) const; + + // Returns true iff all of the contact info in |sub| also appears in |super|. + // Only operates on fields requested in |options|. + bool IsContactEqualOrSuperset(const autofill::AutofillProfile& super, + const autofill::AutofillProfile& sub) const; + + // Returns the number of contact fields requested in |options| which are + // nonempty in |profile|. + int GetContactCompletenessScore( + const autofill::AutofillProfile* profile) const; + + // Returns true iff every contact field requested in |options| is nonempty in + // |profile|. + bool IsContactInfoComplete(const autofill::AutofillProfile* profile) const; + + // Returns profiles for shipping, ordered by completeness. |profiles| should + // be passed in order of frecency, and this order will be preserved among + // equally-complete profiles. + std::vector<autofill::AutofillProfile*> FilterProfilesForShipping( + const std::vector<autofill::AutofillProfile*>& profiles) const; + + int GetShippingCompletenessScore( + const autofill::AutofillProfile* profile) const; + + // Returns true iff every field needed to use |profile| as a shipping address + // is populated. + bool IsShippingComplete(const autofill::AutofillProfile* profile) const; + + // Returns a localized string to be displayed in UI indicating what action, + // if any, must be taken for the given profile to be used as contact info. + base::string16 GetStringForMissingContactFields( + const autofill::AutofillProfile& profile) const; + + // Returns a localized string to be displayed in UI indicating what action, + // if any, must be taken for the given profile to be used as a shipping + // address. + base::string16 GetStringForMissingShippingFields( + const autofill::AutofillProfile& profile) const; + + // Clears the cached evaluation result for |profile|. Must be called when a + // profile is modified and saved during the course of a PaymentRequest. + void Invalidate(const autofill::AutofillProfile& profile); + + private: + ProfileFields ComputeMissingFields( + const autofill::AutofillProfile& profile) const; + ProfileFields GetRequiredProfileFieldsForContact() const; + ProfileFields GetRequiredProfileFieldsForShipping() const; + base::string16 GetStringForMissingFields(ProfileFields fields) const; + bool AreRequiredAddressFieldsPresent( + const autofill::AutofillProfile& profile) const; + + // Comparison functions suitable for sorting profiles by completeness + // score with std::sort. + bool IsContactMoreComplete(const autofill::AutofillProfile* p1, + const autofill::AutofillProfile* p2) const; + bool IsShippingMoreComplete(const autofill::AutofillProfile* p1, + const autofill::AutofillProfile* p2) const; + + mutable std::map<std::string, ProfileFields> cache_; + const PaymentOptionsProvider& options_; +}; + +} // namespace payments + +#endif // COMPONENTS_PAYMENTS_CORE_PAYMENTS_PROFILE_COMPARATOR_H_
\ No newline at end of file diff --git a/chromium/components/payments/core/payments_profile_comparator_unittest.cc b/chromium/components/payments/core/payments_profile_comparator_unittest.cc new file mode 100644 index 00000000000..18f3e58ae15 --- /dev/null +++ b/chromium/components/payments/core/payments_profile_comparator_unittest.cc @@ -0,0 +1,464 @@ +// Copyright 2017 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/payments/core/payments_profile_comparator.h" + +#include <memory> +#include <vector> + +#include "base/guid.h" +#include "base/memory/ptr_util.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill_profile.h" +#include "components/autofill/core/browser/autofill_test_utils.h" +#include "components/payments/core/payment_options_provider.h" +#include "components/strings/grit/components_strings.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/base/l10n/l10n_util.h" + +using autofill::AutofillProfile; + +namespace payments { + +constexpr uint32_t kRequestPayerName = 1 << 0; +constexpr uint32_t kRequestPayerEmail = 1 << 1; +constexpr uint32_t kRequestPayerPhone = 1 << 2; +constexpr uint32_t kRequestShipping = 1 << 3; + +class MockPaymentOptionsProvider : public PaymentOptionsProvider { + public: + MockPaymentOptionsProvider(uint32_t options) : options_(options) {} + + ~MockPaymentOptionsProvider() override {} + bool request_payer_name() const override { + return options_ & kRequestPayerName; + } + bool request_payer_email() const override { + return options_ & kRequestPayerEmail; + } + bool request_payer_phone() const override { + return options_ & kRequestPayerPhone; + } + bool request_shipping() const override { return options_ & kRequestShipping; } + PaymentShippingType shipping_type() const override { + return PaymentShippingType::SHIPPING; + } + + private: + uint32_t options_; +}; + +AutofillProfile CreateProfileWithContactInfo(const char* name, + const char* email, + const char* phone) { + AutofillProfile profile(base::GenerateGUID(), "http://www.example.com/"); + autofill::test::SetProfileInfo(&profile, name, "", "", email, "", "", "", "", + "", "", "", phone); + return profile; +} + +AutofillProfile CreateProfileWithCompleteAddress(const char* name, + const char* phone) { + AutofillProfile profile(base::GenerateGUID(), "http://www.example.com/"); + autofill::test::SetProfileInfo(&profile, name, "", "", "", "", "123 Fake St.", + "", "Fakesville", "MN", "54000", "US", phone); + return profile; +} + +AutofillProfile CreateProfileWithPartialAddress(const char* name, + const char* phone) { + AutofillProfile profile(base::GenerateGUID(), "http://www.example.com/"); + autofill::test::SetProfileInfo(&profile, name, "", "", "", "", "123 Fake St.", + "", "", "", "54000", "", phone); + return profile; +} + +TEST(PaymentRequestProfileUtilTest, FilterProfilesForContact) { + // These profiles are subset/equal, so only the first complete one is + // included. + AutofillProfile exclude_1 = + CreateProfileWithContactInfo("Homer", "", "6515553226"); + + AutofillProfile exclude_2 = + CreateProfileWithContactInfo("Homer", "homer@simpson.net", ""); + + AutofillProfile include_1 = + CreateProfileWithContactInfo("Homer", "homer@simpson.net", "6515553226"); + + AutofillProfile exclude_3 = + CreateProfileWithContactInfo("Homer", "homer@simpson.net", "6515553226"); + + // This profile is different, so it should also be included. Since it is + // less complete than |include_1|, it will appear after. + AutofillProfile include_2 = + CreateProfileWithContactInfo("Marge", "marge@simpson.net", ""); + + // This profile is different, so it should also be included. Since it is + // equally complete with |include_1|, it will appear before |include_2|, but + // after |include_1| since order is preserved amongst profiles of equal + // completeness. + AutofillProfile include_3 = CreateProfileWithContactInfo( + "Bart", "eatmyshorts@simpson.net", "6515553226"); + + std::vector<AutofillProfile*> profiles = {&exclude_1, &exclude_2, &include_1, + &exclude_3, &include_2, &include_3}; + + MockPaymentOptionsProvider provider(kRequestPayerName | kRequestPayerEmail | + kRequestPayerPhone); + PaymentsProfileComparator comp("en-US", provider); + + std::vector<AutofillProfile*> filtered = + comp.FilterProfilesForContact(profiles); + + ASSERT_EQ(3u, filtered.size()); + EXPECT_EQ(&include_1, filtered[0]); + EXPECT_EQ(&include_3, filtered[1]); + EXPECT_EQ(&include_2, filtered[2]); + + // Repeat the filter using a provider set to only request phone numbers. + // Under these rules, since all profiles have the same (or no) phone number, + // we should only see the first profile with a phone number, |exclude_1|. + MockPaymentOptionsProvider phone_only_provider(kRequestPayerPhone); + PaymentsProfileComparator phone_only_comp("en-US", phone_only_provider); + std::vector<AutofillProfile*> filtered_phones = + phone_only_comp.FilterProfilesForContact(profiles); + ASSERT_EQ(1u, filtered_phones.size()); + EXPECT_EQ(&exclude_1, filtered_phones[0]); +} + +TEST(PaymentRequestProfileUtilTest, IsContactEqualOrSuperset) { + MockPaymentOptionsProvider provider(kRequestPayerName | kRequestPayerEmail | + kRequestPayerPhone); + PaymentsProfileComparator comp("en-US", provider); + + AutofillProfile p1 = + CreateProfileWithContactInfo("Homer", "homer@simpson.net", "6515553226"); + + // Candidate subset profile is equal. + AutofillProfile p2 = + CreateProfileWithContactInfo("Homer", "homer@simpson.net", "6515553226"); + EXPECT_TRUE(comp.IsContactEqualOrSuperset(p1, p2)); + EXPECT_TRUE(comp.IsContactEqualOrSuperset(p2, p1)); + + // Candidate subset profile has non-matching fields. + AutofillProfile p3 = CreateProfileWithContactInfo( + "Homer", "homer@springfieldnuclear.gov", "6515553226"); + EXPECT_FALSE(comp.IsContactEqualOrSuperset(p1, p3)); + EXPECT_FALSE(comp.IsContactEqualOrSuperset(p3, p1)); + + // Candidate subset profile is equal, except for missing fields. + AutofillProfile p4 = + CreateProfileWithContactInfo("Homer", "homer@simpson.net", ""); + EXPECT_TRUE(comp.IsContactEqualOrSuperset(p1, p4)); + EXPECT_FALSE(comp.IsContactEqualOrSuperset(p4, p1)); + + // One field is common, but each has a field which the other is missing. + AutofillProfile p5 = + CreateProfileWithContactInfo("Homer", "homer@simpson.net", ""); + AutofillProfile p6 = CreateProfileWithContactInfo("Homer", "", "6515553226"); + EXPECT_FALSE(comp.IsContactEqualOrSuperset(p5, p6)); + EXPECT_FALSE(comp.IsContactEqualOrSuperset(p6, p5)); +} + +TEST(PaymentRequestProfileUtilTest, IsContactEqualOrSuperset_WithFieldIgnored) { + // Discrepancies in email should be ignored throughout this test. + MockPaymentOptionsProvider provider(kRequestPayerName | kRequestPayerPhone); + PaymentsProfileComparator comp("en-US", provider); + + AutofillProfile p1 = + CreateProfileWithContactInfo("Homer", "homer@simpson.net", "6515553226"); + + // Candidate subset profile is equal. + AutofillProfile p2 = + CreateProfileWithContactInfo("Homer", "homer@simpson.net", "6515553226"); + EXPECT_TRUE(comp.IsContactEqualOrSuperset(p1, p2)); + EXPECT_TRUE(comp.IsContactEqualOrSuperset(p2, p1)); + + // Email fields don't match, but profiles are still equal. + AutofillProfile p3 = CreateProfileWithContactInfo( + "Homer", "homer@springfieldnuclear.gov", "6515553226"); + EXPECT_TRUE(comp.IsContactEqualOrSuperset(p1, p3)); + EXPECT_TRUE(comp.IsContactEqualOrSuperset(p3, p1)); + + // Profile without an email is mutual subset of profile with an email. + AutofillProfile p4 = CreateProfileWithContactInfo("Homer", "", "6515553226"); + EXPECT_TRUE(comp.IsContactEqualOrSuperset(p1, p4)); + EXPECT_TRUE(comp.IsContactEqualOrSuperset(p4, p1)); +} + +TEST(PaymentRequestProfileUtilTest, GetContactCompletenessScore) { + MockPaymentOptionsProvider provider(kRequestPayerName | kRequestPayerPhone); + PaymentsProfileComparator comp("en-US", provider); + + // Two completeness points: One each for name and phone number, but not email + // as it was not requested. + AutofillProfile p1 = + CreateProfileWithContactInfo("Homer", "homer@simpson.net", "6515553226"); + EXPECT_EQ(2, comp.GetContactCompletenessScore(&p1)); + + // One completeness point for name, no points for phone number (missing) or + // email (not requested). + AutofillProfile p2 = + CreateProfileWithContactInfo("Homer", "homer@simpson.net", ""); + EXPECT_EQ(1, comp.GetContactCompletenessScore(&p2)); + + // No completeness points, as the only field present was not requested. + AutofillProfile p3 = + CreateProfileWithContactInfo("", "homer@simpson.net", ""); + EXPECT_EQ(0, comp.GetContactCompletenessScore(&p3)); + + // Null profile returns 0. + EXPECT_EQ(0, comp.GetContactCompletenessScore(nullptr)); +} + +TEST(PaymentRequestProfileUtilTest, IsContactInfoComplete) { + MockPaymentOptionsProvider provider(kRequestPayerName | kRequestPayerEmail); + PaymentsProfileComparator comp("en-US", provider); + + // If name and email are present, return true regardless of the (ignored) + // phone value. + AutofillProfile p1 = + CreateProfileWithContactInfo("Homer", "homer@simpson.net", "6515553226"); + AutofillProfile p2 = + CreateProfileWithContactInfo("Homer", "homer@simpson.net", ""); + + EXPECT_TRUE(comp.IsContactInfoComplete(&p1)); + EXPECT_TRUE(comp.IsContactInfoComplete(&p2)); + + // If name is not present, return false regardless of the (ignored) + // phone value. + AutofillProfile p3 = + CreateProfileWithContactInfo("", "homer@simpson.net", "6515553226"); + AutofillProfile p4 = + CreateProfileWithContactInfo("", "homer@simpson.net", ""); + + EXPECT_FALSE(comp.IsContactInfoComplete(&p3)); + EXPECT_FALSE(comp.IsContactInfoComplete(&p4)); + + // If no fields are requested, any profile (even empty or null) is complete. + MockPaymentOptionsProvider empty_provider(0); + PaymentsProfileComparator empty_comp("en-US", empty_provider); + + AutofillProfile p5 = CreateProfileWithContactInfo("", "", ""); + + EXPECT_TRUE(empty_comp.IsContactInfoComplete(&p1)); + EXPECT_TRUE(empty_comp.IsContactInfoComplete(&p5)); + EXPECT_TRUE(empty_comp.IsContactInfoComplete(nullptr)); +} + +TEST(PaymentRequestProfileUtilTest, FilterProfilesForShipping) { + MockPaymentOptionsProvider provider(kRequestShipping); + PaymentsProfileComparator comp("en-US", provider); + + AutofillProfile address_only = CreateProfileWithCompleteAddress("", ""); + + AutofillProfile no_name = CreateProfileWithCompleteAddress("", "6515553226"); + AutofillProfile no_phone = CreateProfileWithCompleteAddress("Homer", ""); + + AutofillProfile empty = CreateProfileWithContactInfo("", "", ""); + + AutofillProfile complete1 = + CreateProfileWithCompleteAddress("Homer", "6515553226"); + + AutofillProfile partial_address = + CreateProfileWithPartialAddress("Homer", "6515553226"); + AutofillProfile no_address = + CreateProfileWithContactInfo("Homer", "", "6515553226"); + + AutofillProfile complete2 = + CreateProfileWithCompleteAddress("Bart", "6515553226"); + + AutofillProfile partial_no_phone = + CreateProfileWithPartialAddress("", "6515553226"); + AutofillProfile partial_no_name = + CreateProfileWithPartialAddress("Homer", ""); + + std::vector<AutofillProfile*> profiles = { + &address_only, &no_name, &no_phone, &empty, + &complete1, &partial_address, &no_address, &complete2, + &partial_no_phone, &partial_no_name}; + + std::vector<AutofillProfile*> filtered = + comp.FilterProfilesForShipping(profiles); + + // Current logic does not remove profiles, only reorder them. + ASSERT_EQ(10u, filtered.size()); + + // First, the complete profiles should be hoisted to the top, keeping their + // relative order. + EXPECT_EQ(&complete1, filtered[0]); + EXPECT_EQ(&complete2, filtered[1]); + + // Next are profiles with a complete address but missing one other field. + EXPECT_EQ(&no_name, filtered[2]); + EXPECT_EQ(&no_phone, filtered[3]); + + // A profile with only a complete address should still appear before profiles + // with partial/empty addresses. + EXPECT_EQ(&address_only, filtered[4]); + + // Profiles with partial/no address then are sorted by whether or not they + // have names and/or phone numbers. + EXPECT_EQ(&partial_address, filtered[5]); + EXPECT_EQ(&no_address, filtered[6]); + + EXPECT_EQ(&partial_no_phone, filtered[7]); + EXPECT_EQ(&partial_no_name, filtered[8]); + + EXPECT_EQ(&empty, filtered[9]); +} + +TEST(PaymentRequestProfileUtilTest, GetShippingCompletenessScore) { + MockPaymentOptionsProvider provider(kRequestShipping); + PaymentsProfileComparator comp("en-US", provider); + + // 12 points for a complete profile: 10 for address, 1 each for name/phone. + AutofillProfile p1 = CreateProfileWithCompleteAddress("Homer", "6515553226"); + EXPECT_EQ(12, comp.GetShippingCompletenessScore(&p1)); + + // 11 points if name or phone is missing. + AutofillProfile p2 = CreateProfileWithCompleteAddress("", "6515553226"); + AutofillProfile p3 = CreateProfileWithCompleteAddress("Homer", ""); + EXPECT_EQ(11, comp.GetShippingCompletenessScore(&p2)); + EXPECT_EQ(11, comp.GetShippingCompletenessScore(&p3)); + + // 10 points for complete address only. + AutofillProfile p4 = CreateProfileWithCompleteAddress("", ""); + EXPECT_EQ(10, comp.GetShippingCompletenessScore(&p4)); + + // 2 points for name and phone without address. + AutofillProfile p5 = CreateProfileWithContactInfo("Homer", "", "6515553226"); + EXPECT_EQ(2, comp.GetShippingCompletenessScore(&p5)); + + // 1 point for name or phone alone. + AutofillProfile p6 = CreateProfileWithContactInfo("Homer", "", ""); + AutofillProfile p7 = CreateProfileWithContactInfo("", "", "6515553226"); + EXPECT_EQ(1, comp.GetShippingCompletenessScore(&p6)); + EXPECT_EQ(1, comp.GetShippingCompletenessScore(&p7)); + + // No points for empty profile, or profile with only a partial address. + AutofillProfile p8 = CreateProfileWithContactInfo("", "", ""); + AutofillProfile p9 = CreateProfileWithPartialAddress("", ""); + EXPECT_EQ(0, comp.GetShippingCompletenessScore(&p8)); + EXPECT_EQ(0, comp.GetShippingCompletenessScore(&p9)); +} + +TEST(PaymentRequestProfileUtilTest, IsShippingComplete) { + MockPaymentOptionsProvider provider(kRequestShipping); + PaymentsProfileComparator comp("en-US", provider); + + // True if name, phone, and address are all populated. + AutofillProfile p1 = CreateProfileWithCompleteAddress("Homer", "6515553226"); + EXPECT_TRUE(comp.IsShippingComplete(&p1)); + + // False if address is partially populated. + AutofillProfile p2 = CreateProfileWithPartialAddress("Homer", "6515553226"); + EXPECT_FALSE(comp.IsShippingComplete(&p2)); + + // False if name isn't populated. + AutofillProfile p3 = CreateProfileWithCompleteAddress("", "6515553226"); + EXPECT_FALSE(comp.IsShippingComplete(&p3)); + + // False if phone isn't populated. + AutofillProfile p4 = CreateProfileWithCompleteAddress("Homer", ""); + EXPECT_FALSE(comp.IsShippingComplete(&p4)); + + // False if only contact info (no address fields) is populated. + AutofillProfile p5 = + CreateProfileWithContactInfo("Homer", "homer@simpson.net", "6515553226"); + EXPECT_FALSE(comp.IsShippingComplete(&p5)); + + MockPaymentOptionsProvider provider_no_shipping(0); + PaymentsProfileComparator comp_no_shipping("en-US", provider_no_shipping); + // nullptr is handled correctly: false if shipping requested, true if not. + EXPECT_FALSE(comp.IsShippingComplete(nullptr)); + EXPECT_TRUE(comp_no_shipping.IsShippingComplete(nullptr)); +} + +TEST(PaymentRequestProfileUtilTest, GetStringForMissingContactFields) { + MockPaymentOptionsProvider provider(kRequestPayerName | kRequestPayerPhone | + kRequestPayerEmail | kRequestShipping); + PaymentsProfileComparator comp("en-US", provider); + + // No error message for complete profile. + AutofillProfile p1 = + CreateProfileWithContactInfo("Homer", "homer@simpson.net", "6515553226"); + EXPECT_TRUE(comp.GetStringForMissingContactFields(p1).empty()); + + MockPaymentOptionsProvider provider_no_email( + kRequestPayerName | kRequestPayerPhone | kRequestShipping); + PaymentsProfileComparator comp_no_email("en-US", provider_no_email); + + // No error message if missing field wasn't required. + AutofillProfile p2 = CreateProfileWithContactInfo("Homer", "", "6515553226"); + EXPECT_TRUE(comp_no_email.GetStringForMissingContactFields(p2).empty()); + + // Error message for email address if email address is missing and required. + EXPECT_EQ(l10n_util::GetStringUTF16(IDS_PAYMENTS_EMAIL_REQUIRED), + comp.GetStringForMissingContactFields(p2)); + + // Error message for phone number if phone is missing and required. + AutofillProfile p3 = + CreateProfileWithContactInfo("Homer", "homer@simpson.net", ""); + EXPECT_EQ(l10n_util::GetStringUTF16(IDS_PAYMENTS_PHONE_NUMBER_REQUIRED), + comp.GetStringForMissingContactFields(p3)); + + // Error message for name if name is missing and required. + AutofillProfile p4 = + CreateProfileWithContactInfo("", "homer@simpson.net", "6515553226"); + EXPECT_EQ(l10n_util::GetStringUTF16(IDS_PAYMENTS_NAME_REQUIRED), + comp.GetStringForMissingContactFields(p4)); + + // Generic error message if multiple fields missing. + AutofillProfile p5 = + CreateProfileWithContactInfo("", "homer@simpson.net", ""); + EXPECT_EQ(l10n_util::GetStringUTF16(IDS_PAYMENTS_MORE_INFORMATION_REQUIRED), + comp.GetStringForMissingContactFields(p5)); +} + +TEST(PaymentRequestProfileUtilTest, GetStringForMissingShippingFields) { + MockPaymentOptionsProvider provider(kRequestPayerName | kRequestPayerPhone | + kRequestPayerEmail | kRequestShipping); + PaymentsProfileComparator comp("en-US", provider); + + // No error message for complete profile. + AutofillProfile p1 = CreateProfileWithCompleteAddress("Homer", "6515553226"); + EXPECT_TRUE(comp.GetStringForMissingShippingFields(p1).empty()); + + // Error message for shipping if shipping requested and not present. + AutofillProfile p2 = + CreateProfileWithContactInfo("Homer", "homer@simpson.net", "6515553226"); + EXPECT_EQ(l10n_util::GetStringUTF16(IDS_PAYMENTS_INVALID_ADDRESS), + comp.GetStringForMissingShippingFields(p2)); + + // Error message for shipping if shipping requested and only partially + // complete. + AutofillProfile p3 = CreateProfileWithPartialAddress("Homer", "6515553226"); + EXPECT_EQ(l10n_util::GetStringUTF16(IDS_PAYMENTS_INVALID_ADDRESS), + comp.GetStringForMissingShippingFields(p3)); + + // Error message for name if name requested and missing. + AutofillProfile p4 = CreateProfileWithCompleteAddress("", "6515553226"); + EXPECT_EQ(l10n_util::GetStringUTF16(IDS_PAYMENTS_NAME_REQUIRED), + comp.GetStringForMissingShippingFields(p4)); + + // Error message for phone if phone requested and missing. + AutofillProfile p5 = CreateProfileWithCompleteAddress("Homer", ""); + EXPECT_EQ(l10n_util::GetStringUTF16(IDS_PAYMENTS_PHONE_NUMBER_REQUIRED), + comp.GetStringForMissingShippingFields(p5)); + + // Generic error message if multiple fields missing. + AutofillProfile p6 = CreateProfileWithContactInfo("", "", ""); + EXPECT_EQ(l10n_util::GetStringUTF16(IDS_PAYMENTS_MORE_INFORMATION_REQUIRED), + comp.GetStringForMissingShippingFields(p6)); + + MockPaymentOptionsProvider provider_no_shipping( + kRequestPayerName | kRequestPayerPhone | kRequestPayerEmail); + PaymentsProfileComparator comp_no_shipping("en-US", provider_no_shipping); + + // No error message if everything is missing but shipping wasn't requested. + EXPECT_TRUE(comp_no_shipping.GetStringForMissingShippingFields(p6).empty()); +} + +} // namespace payments diff --git a/chromium/components/payments/core/profile_util.cc b/chromium/components/payments/core/profile_util.cc deleted file mode 100644 index c1149c85ed8..00000000000 --- a/chromium/components/payments/core/profile_util.cc +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright 2017 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/payments/core/profile_util.h" - -#include <algorithm> - -#include "components/autofill/core/browser/autofill_profile.h" -#include "components/autofill/core/browser/field_types.h" -#include "components/payments/core/payment_options_provider.h" - -namespace payments { -namespace profile_util { - -std::vector<autofill::AutofillProfile*> FilterProfilesForContact( - const std::vector<autofill::AutofillProfile*>& profiles, - const std::string& app_locale, - const PaymentOptionsProvider& options) { - // We will be removing profiles, so we operate on a copy. - std::vector<autofill::AutofillProfile*> processed = profiles; - - PaymentsProfileComparator comparator(app_locale, options); - - // Stable sort, since profiles are expected to be passed in frecency order. - std::stable_sort( - processed.begin(), processed.end(), - std::bind(&PaymentsProfileComparator::IsContactMoreComplete, &comparator, - std::placeholders::_1, std::placeholders::_2)); - - auto it = processed.begin(); - while (it != processed.end()) { - if (comparator.GetContactCompletenessScore(*it) == 0) { - // Since profiles are sorted by completeness, this and any further - // profiles can be discarded. - processed.erase(it, processed.end()); - break; - } - - // Attempt to find a matching element in the vector before the current. - // This is quadratic, but the number of elements is generally small - // (< 10), so a more complicated algorithm would be overkill. - if (std::find_if(processed.begin(), it, - [&](autofill::AutofillProfile* prior) { - return comparator.IsContactEqualOrSuperset(*prior, **it); - }) != it) { - // Remove the subset profile. |it| will point to the next element after - // erasure. - it = processed.erase(it); - } else { - it++; - } - } - - return processed; -} - -PaymentsProfileComparator::PaymentsProfileComparator( - const std::string& app_locale, - const PaymentOptionsProvider& options) - : autofill::AutofillProfileComparator(app_locale), options_(options) {} - -PaymentsProfileComparator::~PaymentsProfileComparator() {} - -bool PaymentsProfileComparator::IsContactEqualOrSuperset( - const autofill::AutofillProfile& super, - const autofill::AutofillProfile& sub) { - if (options_.request_payer_name()) { - if (sub.HasInfo(autofill::NAME_FULL) && - !super.HasInfo(autofill::NAME_FULL)) { - return false; - } - if (!HaveMergeableNames(super, sub)) - return false; - } - if (options_.request_payer_phone()) { - if (sub.HasInfo(autofill::PHONE_HOME_WHOLE_NUMBER) && - !super.HasInfo(autofill::PHONE_HOME_WHOLE_NUMBER)) { - return false; - } - if (!HaveMergeablePhoneNumbers(super, sub)) - return false; - } - if (options_.request_payer_email()) { - if (sub.HasInfo(autofill::EMAIL_ADDRESS) && - !super.HasInfo(autofill::EMAIL_ADDRESS)) { - return false; - } - if (!HaveMergeableEmailAddresses(super, sub)) - return false; - } - return true; -} - -int PaymentsProfileComparator::GetContactCompletenessScore( - const autofill::AutofillProfile* profile) { - if (!profile) - return 0; - - return (options_.request_payer_name() && - profile->HasInfo(autofill::NAME_FULL)) + - (options_.request_payer_phone() && - profile->HasInfo(autofill::PHONE_HOME_WHOLE_NUMBER)) + - (options_.request_payer_email() && - profile->HasInfo(autofill::EMAIL_ADDRESS)); -} - -bool PaymentsProfileComparator::IsContactInfoComplete( - const autofill::AutofillProfile* profile) { - int desired_score = options_.request_payer_name() + - options_.request_payer_phone() + - options_.request_payer_email(); - return GetContactCompletenessScore(profile) == desired_score; -} - -bool PaymentsProfileComparator::IsContactMoreComplete( - const autofill::AutofillProfile* p1, - const autofill::AutofillProfile* p2) { - return GetContactCompletenessScore(p1) > GetContactCompletenessScore(p2); -} - -} // namespace profile_util -} // namespace payments diff --git a/chromium/components/payments/core/profile_util.h b/chromium/components/payments/core/profile_util.h deleted file mode 100644 index 6693fe854c8..00000000000 --- a/chromium/components/payments/core/profile_util.h +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2017 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_PAYMENTS_CONTENT_PROFILE_UTIL_H_ -#define COMPONENTS_PAYMENTS_CONTENT_PROFILE_UTIL_H_ - -#include <string> -#include <vector> - -#include "components/autofill/core/browser/autofill_profile_comparator.h" - -// Utility functions used for processing and filtering address profiles -// (AutofillProfile). - -namespace autofill { -class AutofillProfile; -} // namespace autofill - -namespace payments { - -class PaymentOptionsProvider; - -namespace profile_util { - -// Returns profiles for contact info, ordered by completeness and deduplicated. -// |profiles| should be passed in order of frecency, and this order will be -// preserved among equally-complete profiles. Deduplication here means that -// profiles returned are excluded if they are a subset of a more complete or -// more frecent profile. Completeness here refers only to the presence of the -// fields requested per the request_payer_* fields in |options|. -std::vector<autofill::AutofillProfile*> FilterProfilesForContact( - const std::vector<autofill::AutofillProfile*>& profiles, - const std::string& app_locale, - const PaymentOptionsProvider& options); - -// Helper class which evaluates profiles for similarity and completeness. -class PaymentsProfileComparator : public autofill::AutofillProfileComparator { - public: - PaymentsProfileComparator(const std::string& app_locale, - const PaymentOptionsProvider& options); - ~PaymentsProfileComparator(); - - // Returns true iff all of the contact info in |sub| also appears in |super|. - // Only operates on fields requested in |options|. - bool IsContactEqualOrSuperset(const autofill::AutofillProfile& super, - const autofill::AutofillProfile& sub); - - // Returns the number of contact fields requested in |options| which are - // nonempty in |profile|. - int GetContactCompletenessScore(const autofill::AutofillProfile* profile); - - // Returns true iff every contact field requested in |options| is nonempty in - // |profile|. - bool IsContactInfoComplete(const autofill::AutofillProfile* profile); - - // Comparison function suitable for sorting profiles by contact completeness - // score with std::sort. - bool IsContactMoreComplete(const autofill::AutofillProfile* p1, - const autofill::AutofillProfile* p2); - - private: - const PaymentOptionsProvider& options_; -}; - -} // namespace profile_util -} // namespace payments - -#endif // COMPONENTS_PAYMENTS_CONTENT_PROFILE_UTIL_H_
\ No newline at end of file diff --git a/chromium/components/payments/core/profile_util_unittest.cc b/chromium/components/payments/core/profile_util_unittest.cc deleted file mode 100644 index 5ff1d7ec4e3..00000000000 --- a/chromium/components/payments/core/profile_util_unittest.cc +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright 2017 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/payments/core/profile_util.h" - -#include <memory> -#include <vector> - -#include "base/guid.h" -#include "base/memory/ptr_util.h" -#include "base/strings/utf_string_conversions.h" -#include "components/autofill/core/browser/autofill_profile.h" -#include "components/autofill/core/browser/autofill_test_utils.h" -#include "components/payments/core/payment_options_provider.h" -#include "testing/gtest/include/gtest/gtest.h" - -using autofill::AutofillProfile; - -namespace payments { -namespace profile_util { - -constexpr uint32_t kRequestPayerName = 1 << 0; -constexpr uint32_t kRequestPayerEmail = 1 << 1; -constexpr uint32_t kRequestPayerPhone = 1 << 2; -constexpr uint32_t kRequestShipping = 1 << 3; - -class MockPaymentOptionsProvider : public PaymentOptionsProvider { - public: - MockPaymentOptionsProvider(uint32_t options) : options_(options) {} - - ~MockPaymentOptionsProvider() override {} - bool request_payer_name() const override { - return options_ & kRequestPayerName; - } - bool request_payer_email() const override { - return options_ & kRequestPayerEmail; - } - bool request_payer_phone() const override { - return options_ & kRequestPayerPhone; - } - bool request_shipping() const override { return options_ & kRequestShipping; } - PaymentShippingType shipping_type() const override { - return PaymentShippingType::SHIPPING; - } - - private: - uint32_t options_; -}; - -AutofillProfile CreateProfileWithContactInfo(const char* name, - const char* email, - const char* phone) { - AutofillProfile profile(base::GenerateGUID(), "http://www.example.com/"); - autofill::test::SetProfileInfo(&profile, name, "", "", email, "", "", "", "", - "", "", "", phone); - return profile; -} - -TEST(PaymentRequestProfileUtilTest, FilterProfilesForContact) { - // These profiles are subset/equal, so only the first complete one is - // included. - AutofillProfile exclude_1 = - CreateProfileWithContactInfo("Homer", "", "5551234567"); - - AutofillProfile exclude_2 = - CreateProfileWithContactInfo("Homer", "homer@simpson.net", ""); - - AutofillProfile include_1 = - CreateProfileWithContactInfo("Homer", "homer@simpson.net", "5551234567"); - - AutofillProfile exclude_3 = - CreateProfileWithContactInfo("Homer", "homer@simpson.net", "5551234567"); - - // This profile is different, so it should also be included. Since it is - // less complete than |include_1|, it will appear after. - AutofillProfile include_2 = - CreateProfileWithContactInfo("Marge", "marge@simpson.net", ""); - - // This profile is different, so it should also be included. Since it is - // equally complete with |include_1|, it will appear before |include_2|, but - // after |include_1| since order is preserved amongst profiles of equal - // completeness. - AutofillProfile include_3 = CreateProfileWithContactInfo( - "Bart", "eatmyshorts@simpson.net", "5551234567"); - - std::vector<AutofillProfile*> profiles = {&exclude_1, &exclude_2, &include_1, - &exclude_3, &include_2, &include_3}; - - MockPaymentOptionsProvider provider(kRequestPayerName | kRequestPayerEmail | - kRequestPayerPhone); - std::vector<AutofillProfile*> filtered = - FilterProfilesForContact(profiles, "en-US", provider); - - ASSERT_EQ(3u, filtered.size()); - EXPECT_EQ(&include_1, filtered[0]); - EXPECT_EQ(&include_3, filtered[1]); - EXPECT_EQ(&include_2, filtered[2]); - - // Repeat the filter using a provider set to only request phone numbers. - // Under these rules, since all profiles have the same (or no) phone number, - // we should only see the first profile with a phone number, |exclude_1|. - MockPaymentOptionsProvider phone_only_provider(kRequestPayerPhone); - std::vector<AutofillProfile*> filtered_phones = - FilterProfilesForContact(profiles, "en-US", phone_only_provider); - ASSERT_EQ(1u, filtered_phones.size()); - EXPECT_EQ(&exclude_1, filtered_phones[0]); -} - -TEST(PaymentRequestProfileUtilTest, IsContactEqualOrSuperset) { - MockPaymentOptionsProvider provider(kRequestPayerName | kRequestPayerEmail | - kRequestPayerPhone); - PaymentsProfileComparator comp("en-US", provider); - - AutofillProfile p1 = - CreateProfileWithContactInfo("Homer", "homer@simpson.net", "5551234567"); - - // Candidate subset profile is equal. - AutofillProfile p2 = - CreateProfileWithContactInfo("Homer", "homer@simpson.net", "5551234567"); - EXPECT_TRUE(comp.IsContactEqualOrSuperset(p1, p2)); - EXPECT_TRUE(comp.IsContactEqualOrSuperset(p2, p1)); - - // Candidate subset profile has non-matching fields. - AutofillProfile p3 = CreateProfileWithContactInfo( - "Homer", "homer@springfieldnuclear.gov", "5551234567"); - EXPECT_FALSE(comp.IsContactEqualOrSuperset(p1, p3)); - EXPECT_FALSE(comp.IsContactEqualOrSuperset(p3, p1)); - - // Candidate subset profile is equal, except for missing fields. - AutofillProfile p4 = - CreateProfileWithContactInfo("Homer", "homer@simpson.net", ""); - EXPECT_TRUE(comp.IsContactEqualOrSuperset(p1, p4)); - EXPECT_FALSE(comp.IsContactEqualOrSuperset(p4, p1)); - - // One field is common, but each has a field which the other is missing. - AutofillProfile p5 = - CreateProfileWithContactInfo("Homer", "homer@simpson.net", ""); - AutofillProfile p6 = CreateProfileWithContactInfo("Homer", "", "5551234567"); - EXPECT_FALSE(comp.IsContactEqualOrSuperset(p5, p6)); - EXPECT_FALSE(comp.IsContactEqualOrSuperset(p6, p5)); -} - -TEST(PaymentRequestProfileUtilTest, IsContactEqualOrSuperset_WithFieldIgnored) { - // Discrepancies in email should be ignored throughout this test. - MockPaymentOptionsProvider provider(kRequestPayerName | kRequestPayerPhone); - PaymentsProfileComparator comp("en-US", provider); - - AutofillProfile p1 = - CreateProfileWithContactInfo("Homer", "homer@simpson.net", "5551234567"); - - // Candidate subset profile is equal. - AutofillProfile p2 = - CreateProfileWithContactInfo("Homer", "homer@simpson.net", "5551234567"); - EXPECT_TRUE(comp.IsContactEqualOrSuperset(p1, p2)); - EXPECT_TRUE(comp.IsContactEqualOrSuperset(p2, p1)); - - // Email fields don't match, but profiles are still equal. - AutofillProfile p3 = CreateProfileWithContactInfo( - "Homer", "homer@springfieldnuclear.gov", "5551234567"); - EXPECT_TRUE(comp.IsContactEqualOrSuperset(p1, p3)); - EXPECT_TRUE(comp.IsContactEqualOrSuperset(p3, p1)); - - // Profile without an email is mutual subset of profile with an email. - AutofillProfile p4 = CreateProfileWithContactInfo("Homer", "", "5551234567"); - EXPECT_TRUE(comp.IsContactEqualOrSuperset(p1, p4)); - EXPECT_TRUE(comp.IsContactEqualOrSuperset(p4, p1)); -} - -TEST(PaymentRequestProfileUtilTest, GetContactCompletenessScore) { - MockPaymentOptionsProvider provider(kRequestPayerName | kRequestPayerPhone); - PaymentsProfileComparator comp("en-US", provider); - - // Two completeness points: One each for name and phone number, but not email - // as it was not requested. - AutofillProfile p1 = - CreateProfileWithContactInfo("Homer", "homer@simpson.net", "5551234567"); - EXPECT_EQ(2, comp.GetContactCompletenessScore(&p1)); - - // One completeness point for name, no points for phone number (missing) or - // email (not requested). - AutofillProfile p2 = - CreateProfileWithContactInfo("Homer", "homer@simpson.net", ""); - EXPECT_EQ(1, comp.GetContactCompletenessScore(&p2)); - - // No completeness points, as the only field present was not requested. - AutofillProfile p3 = - CreateProfileWithContactInfo("", "homer@simpson.net", ""); - EXPECT_EQ(0, comp.GetContactCompletenessScore(&p3)); - - // Null profile returns 0. - EXPECT_EQ(0, comp.GetContactCompletenessScore(nullptr)); -} - -TEST(PaymentRequestProfileUtilTest, IsContactInfoComplete) { - MockPaymentOptionsProvider provider(kRequestPayerName | kRequestPayerEmail); - PaymentsProfileComparator comp("en-US", provider); - - // If name and email are present, return true regardless of the (ignored) - // phone value. - AutofillProfile p1 = - CreateProfileWithContactInfo("Homer", "homer@simpson.net", "5551234567"); - AutofillProfile p2 = - CreateProfileWithContactInfo("Homer", "homer@simpson.net", ""); - - EXPECT_TRUE(comp.IsContactInfoComplete(&p1)); - EXPECT_TRUE(comp.IsContactInfoComplete(&p2)); - - // If name is not present, return false regardless of the (ignored) - // phone value. - AutofillProfile p3 = - CreateProfileWithContactInfo("", "homer@simpson.net", "5551234567"); - AutofillProfile p4 = - CreateProfileWithContactInfo("", "homer@simpson.net", ""); - - EXPECT_FALSE(comp.IsContactInfoComplete(&p3)); - EXPECT_FALSE(comp.IsContactInfoComplete(&p4)); - - // If no fields are requested, any profile (even empty or null) is complete. - MockPaymentOptionsProvider empty_provider(0); - PaymentsProfileComparator empty_comp("en-US", empty_provider); - - AutofillProfile p5 = CreateProfileWithContactInfo("", "", ""); - - EXPECT_TRUE(empty_comp.IsContactInfoComplete(&p1)); - EXPECT_TRUE(empty_comp.IsContactInfoComplete(&p5)); - EXPECT_TRUE(empty_comp.IsContactInfoComplete(nullptr)); -} - -} // namespace profile_util -} // namespace payments diff --git a/chromium/components/payments/core/strings_util.cc b/chromium/components/payments/core/strings_util.cc index 8c405e5ba83..165d2b225b7 100644 --- a/chromium/components/payments/core/strings_util.cc +++ b/chromium/components/payments/core/strings_util.cc @@ -4,12 +4,53 @@ #include "components/payments/core/strings_util.h" +#include <vector> + #include "base/logging.h" +#include "components/autofill/core/browser/autofill_profile.h" +#include "components/autofill/core/browser/field_types.h" #include "components/strings/grit/components_strings.h" #include "ui/base/l10n/l10n_util.h" namespace payments { +base::string16 GetShippingAddressLabelFormAutofillProfile( + const autofill::AutofillProfile& profile, + const std::string& locale) { + // Name, phone number, and country are not included in the shipping address + // label. + static const std::vector<autofill::ServerFieldType> label_fields{ + autofill::COMPANY_NAME, + autofill::ADDRESS_HOME_STREET_ADDRESS, + autofill::ADDRESS_HOME_DEPENDENT_LOCALITY, + autofill::ADDRESS_HOME_CITY, + autofill::ADDRESS_HOME_STATE, + autofill::ADDRESS_HOME_ZIP, + autofill::ADDRESS_HOME_SORTING_CODE, + }; + + return profile.ConstructInferredLabel(label_fields, label_fields.size(), + locale); +} + +base::string16 GetBillingAddressLabelFromAutofillProfile( + const autofill::AutofillProfile& profile, + const std::string& locale) { + // Name, company, phone number, and country are not included in the billing + // address label. + static const std::vector<autofill::ServerFieldType> label_fields{ + autofill::ADDRESS_HOME_STREET_ADDRESS, + autofill::ADDRESS_HOME_DEPENDENT_LOCALITY, + autofill::ADDRESS_HOME_CITY, + autofill::ADDRESS_HOME_STATE, + autofill::ADDRESS_HOME_ZIP, + autofill::ADDRESS_HOME_SORTING_CODE, + }; + + return profile.ConstructInferredLabel(label_fields, label_fields.size(), + locale); +} + base::string16 GetShippingAddressSelectorInfoMessage( PaymentShippingType shipping_type) { switch (shipping_type) { diff --git a/chromium/components/payments/core/strings_util.h b/chromium/components/payments/core/strings_util.h index 3d72567c44c..c693d29fe6d 100644 --- a/chromium/components/payments/core/strings_util.h +++ b/chromium/components/payments/core/strings_util.h @@ -5,11 +5,27 @@ #ifndef COMPONENTS_PAYMENTS_CORE_STRINGS_UTIL_H_ #define COMPONENTS_PAYMENTS_CORE_STRINGS_UTIL_H_ +#include <string> + #include "base/strings/string16.h" #include "components/payments/core/payment_options_provider.h" +namespace autofill { +class AutofillProfile; +} + namespace payments { +// Helper function to create a shipping address label from an autofill profile. +base::string16 GetShippingAddressLabelFormAutofillProfile( + const autofill::AutofillProfile& profile, + const std::string& locale); + +// Helper function to create a billing address label from an autofill profile. +base::string16 GetBillingAddressLabelFromAutofillProfile( + const autofill::AutofillProfile& profile, + const std::string& locale); + // Gets the informational message to be displayed in the shipping address // selector view when there are no valid shipping options. base::string16 GetShippingAddressSelectorInfoMessage( diff --git a/chromium/components/payments/core/subkey_requester.cc b/chromium/components/payments/core/subkey_requester.cc new file mode 100644 index 00000000000..513fc54ffc4 --- /dev/null +++ b/chromium/components/payments/core/subkey_requester.cc @@ -0,0 +1,129 @@ +// Copyright 2017 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/payments/core/subkey_requester.h" + +#include <memory> +#include <utility> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/cancelable_callback.h" +#include "base/memory/ptr_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/sequenced_task_runner_handle.h" +#include "base/time/time.h" +#include "third_party/libaddressinput/chromium/chrome_address_validator.h" +#include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_data.h" +#include "third_party/libaddressinput/src/cpp/include/libaddressinput/source.h" +#include "third_party/libaddressinput/src/cpp/include/libaddressinput/storage.h" +namespace payments { + +namespace { + +using ::i18n::addressinput::Source; +using ::i18n::addressinput::Storage; + +class SubKeyRequest : public SubKeyRequester::Request { + public: + // The |delegate| and |address_validator| need to outlive this Request. + SubKeyRequest(const std::string& region_code, + int timeout_seconds, + autofill::AddressValidator* address_validator, + SubKeyReceiverCallback on_subkeys_received) + : region_code_(region_code), + address_validator_(address_validator), + on_subkeys_received_(std::move(on_subkeys_received)), + has_responded_(false), + on_timeout_(base::Bind(&::payments::SubKeyRequest::OnRulesLoaded, + base::Unretained(this))) { + base::SequencedTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, on_timeout_.callback(), + base::TimeDelta::FromSeconds(timeout_seconds)); + } + + ~SubKeyRequest() override {} + + void OnRulesLoaded() override { + on_timeout_.Cancel(); + // Check if the timeout happened before the rules were loaded. + if (has_responded_) + return; + has_responded_ = true; + + std::move(on_subkeys_received_) + .Run(address_validator_->GetRegionSubKeys(region_code_)); + } + + private: + std::string region_code_; + // Not owned. Never null. Outlive this object. + autofill::AddressValidator* address_validator_; + + SubKeyReceiverCallback on_subkeys_received_; + + bool has_responded_; + base::CancelableCallback<void()> on_timeout_; + + DISALLOW_COPY_AND_ASSIGN(SubKeyRequest); +}; + +} // namespace + +SubKeyRequester::SubKeyRequester(std::unique_ptr<Source> source, + std::unique_ptr<Storage> storage) + : address_validator_(std::move(source), std::move(storage), this) {} + +SubKeyRequester::~SubKeyRequester() {} + +void SubKeyRequester::StartRegionSubKeysRequest(const std::string& region_code, + int timeout_seconds, + SubKeyReceiverCallback cb) { + DCHECK(timeout_seconds >= 0); + + std::unique_ptr<SubKeyRequest> request(base::MakeUnique<SubKeyRequest>( + region_code, timeout_seconds, &address_validator_, std::move(cb))); + + if (AreRulesLoadedForRegion(region_code)) { + request->OnRulesLoaded(); + } else { + // Setup the variables so that the subkeys request is sent, when the rules + // are loaded. + pending_subkey_region_code_ = region_code; + pending_subkey_request_ = std::move(request); + + // Start loading the rules for that region. If the rules were already in the + // process of being loaded, this call will do nothing. + LoadRulesForRegion(region_code); + } +} + +bool SubKeyRequester::AreRulesLoadedForRegion(const std::string& region_code) { + return address_validator_.AreRulesLoadedForRegion(region_code); +} + +void SubKeyRequester::LoadRulesForRegion(const std::string& region_code) { + address_validator_.LoadRules(region_code); +} + +void SubKeyRequester::OnAddressValidationRulesLoaded( + const std::string& region_code, + bool success) { + // The case for |success| == false is already handled. if |success| == false, + // AddressValidator::GetRegionSubKeys will return an empty list of subkeys. + // Therefore, here, we can ignore the value of |success|. + + // Check if there is any subkey request for that region code. + if (!pending_subkey_region_code_.compare(region_code)) + pending_subkey_request_->OnRulesLoaded(); + pending_subkey_region_code_.clear(); + pending_subkey_request_.reset(); +} + +void SubKeyRequester::CancelPendingGetSubKeys() { + pending_subkey_region_code_.clear(); + pending_subkey_request_.reset(); +} + +} // namespace payments diff --git a/chromium/components/payments/core/subkey_requester.h b/chromium/components/payments/core/subkey_requester.h new file mode 100644 index 00000000000..1813781d5b4 --- /dev/null +++ b/chromium/components/payments/core/subkey_requester.h @@ -0,0 +1,73 @@ +// Copyright 2017 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_PAYMENTS_CORE_SUBKEY_REQUESTER_H_ +#define COMPONENTS_PAYMENTS_CORE_SUBKEY_REQUESTER_H_ + +#include "base/macros.h" +#include "third_party/libaddressinput/chromium/chrome_address_validator.h" + +namespace payments { + +using SubKeyReceiverCallback = + base::OnceCallback<void(const std::vector<std::string>&)>; + +// SubKeyRequester Loads Rules from the server and extracts the subkeys. +// For a given key (region code for a country, such as US), the list of its +// corresponding subkeys is the list of that countries admin areas (states, +// provinces, ..). +class SubKeyRequester : public autofill::LoadRulesListener { + public: + // The interface for the subkey request. + class Request { + public: + virtual void OnRulesLoaded() = 0; + virtual ~Request() {} + }; + + SubKeyRequester(std::unique_ptr<i18n::addressinput::Source> source, + std::unique_ptr<i18n::addressinput::Storage> storage); + ~SubKeyRequester() override; + + // If the rules for |region_code| are loaded, this gets the subkeys for the + // |region_code|, synchronously. If they are not loaded yet, it sets up a + // task to get the subkeys when the rules are loaded (asynchronous). If the + // loading has not yet started, it will also start loading the rules for the + // |region_code|. The received subkeys will be returned to the |requester|. If + // the subkeys are not received in |timeout_seconds|, then the requester will + // be informed and the request will be canceled. |requester| should never be + // null. + void StartRegionSubKeysRequest(const std::string& region_code, + int timeout_seconds, + SubKeyReceiverCallback cb); + + // Returns whether the rules for the specified |region_code| have finished + // loading. + bool AreRulesLoadedForRegion(const std::string& region_code); + + // Start loading the rules for the specified |region_code|. + virtual void LoadRulesForRegion(const std::string& region_code); + + // Cancels the pending subkey request task. + void CancelPendingGetSubKeys(); + + private: + // Called when the address rules for the |region_code| have finished + // loading. Implementation of the LoadRulesListener interface. + void OnAddressValidationRulesLoaded(const std::string& region_code, + bool success) override; + + // The region code and the request for the pending subkey request. + std::unique_ptr<Request> pending_subkey_request_; + std::string pending_subkey_region_code_; + + // The address validator used to load subkeys. + autofill::AddressValidator address_validator_; + + DISALLOW_COPY_AND_ASSIGN(SubKeyRequester); +}; + +} // namespace payments + +#endif // COMPONENTS_PAYMENTS_CORE_SUBKEY_REQUESTER_H_ diff --git a/chromium/components/payments/core/subkey_requester_unittest.cc b/chromium/components/payments/core/subkey_requester_unittest.cc new file mode 100644 index 00000000000..f679c16a6b0 --- /dev/null +++ b/chromium/components/payments/core/subkey_requester_unittest.cc @@ -0,0 +1,194 @@ +// Copyright 2017 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/payments/core/subkey_requester.h" + +#include <utility> + +#include "base/bind.h" +#include "base/run_loop.h" +#include "base/strings/utf_string_conversions.h" +#include "base/test/scoped_task_scheduler.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/libaddressinput/src/cpp/include/libaddressinput/null_storage.h" +#include "third_party/libaddressinput/src/cpp/include/libaddressinput/source.h" +#include "third_party/libaddressinput/src/cpp/include/libaddressinput/storage.h" +#include "third_party/libaddressinput/src/cpp/test/testdata_source.h" + +namespace payments { +namespace { + +using ::i18n::addressinput::NullStorage; +using ::i18n::addressinput::Source; +using ::i18n::addressinput::Storage; +using ::i18n::addressinput::TestdataSource; + +const char kLocale[] = "OZ"; +const int kInvalidSize = -1; +const int kCorrectSize = 2; // for subkeys = Do, Re +const int kEmptySize = 0; + +class SubKeyReceiver : public base::RefCountedThreadSafe<SubKeyReceiver> { + public: + SubKeyReceiver() : subkeys_size_(kInvalidSize) {} + + void OnSubKeysReceived(const std::vector<std::string>& subkeys) { + subkeys_size_ = subkeys.size(); + } + + int subkeys_size() const { return subkeys_size_; } + + private: + friend class base::RefCountedThreadSafe<SubKeyReceiver>; + ~SubKeyReceiver() {} + + int subkeys_size_; + + DISALLOW_COPY_AND_ASSIGN(SubKeyReceiver); +}; + +// Used to load region rules for this test. +class ChromiumTestdataSource : public TestdataSource { + public: + ChromiumTestdataSource() : TestdataSource(true) {} + + ~ChromiumTestdataSource() override {} + + // For this test, only load the rules for the kLocale. + void Get(const std::string& key, const Callback& data_ready) const override { + data_ready( + true, key, + new std::string( + "{\"data/OZ\": " + "{\"id\":\"data/OZ\",\"key\":\"OZ\",\"name\":\"Oz \", " + "\"lang\":\"en\",\"languages\":\"en\",\"sub_keys\":\"DO~Re\"}}")); + } + + private: + DISALLOW_COPY_AND_ASSIGN(ChromiumTestdataSource); +}; + +// A test subclass of the SubKeyRequesterImpl. Used to simulate rules not +// being loaded. +class TestSubKeyRequester : public SubKeyRequester { + public: + TestSubKeyRequester(std::unique_ptr<::i18n::addressinput::Source> source, + std::unique_ptr<::i18n::addressinput::Storage> storage) + : SubKeyRequester(std::move(source), std::move(storage)), + should_load_rules_(true) {} + + ~TestSubKeyRequester() override {} + + void ShouldLoadRules(bool should_load_rules) { + should_load_rules_ = should_load_rules; + } + + void LoadRulesForRegion(const std::string& region_code) override { + if (should_load_rules_) { + SubKeyRequester::LoadRulesForRegion(region_code); + } + } + + private: + bool should_load_rules_; + base::test::ScopedTaskScheduler scoped_task_scheduler_; + + DISALLOW_COPY_AND_ASSIGN(TestSubKeyRequester); +}; + +} // namespace + +class SubKeyRequesterTest : public testing::Test { + protected: + SubKeyRequesterTest() + : requester_(new TestSubKeyRequester( + std::unique_ptr<Source>(new ChromiumTestdataSource), + std::unique_ptr<Storage>(new NullStorage))) {} + + ~SubKeyRequesterTest() override {} + + const std::unique_ptr<TestSubKeyRequester> requester_; + + private: + DISALLOW_COPY_AND_ASSIGN(SubKeyRequesterTest); +}; + +// Tests that rules are not loaded by default. +TEST_F(SubKeyRequesterTest, AreRulesLoadedForRegion_NotLoaded) { + EXPECT_FALSE(requester_->AreRulesLoadedForRegion(kLocale)); +} + +// Tests that the rules are loaded correctly. +TEST_F(SubKeyRequesterTest, AreRulesLoadedForRegion_Loaded) { + requester_->LoadRulesForRegion(kLocale); + EXPECT_TRUE(requester_->AreRulesLoadedForRegion(kLocale)); +} + +// Tests that if the rules are loaded before the subkey request is started, the +// received subkeys will be returned to the delegate synchronously. +TEST_F(SubKeyRequesterTest, StartRequest_RulesLoaded) { + scoped_refptr<SubKeyReceiver> subkey_receiver_ = new SubKeyReceiver(); + + SubKeyReceiverCallback cb = + base::BindOnce(&SubKeyReceiver::OnSubKeysReceived, subkey_receiver_); + + // Load the rules. + requester_->LoadRulesForRegion(kLocale); + EXPECT_TRUE(requester_->AreRulesLoadedForRegion(kLocale)); + + // Start the request. + requester_->StartRegionSubKeysRequest(kLocale, 0, std::move(cb)); + + // Since the rules are already loaded, the subkeys should be received + // synchronously. + EXPECT_EQ(subkey_receiver_->subkeys_size(), kCorrectSize); +} + +// Tests that if the rules are not loaded before the request and cannot be +// loaded after, the subkeys will not be received and the delegate will be +// notified. +TEST_F(SubKeyRequesterTest, StartRequest_RulesNotLoaded_WillNotLoad) { + scoped_refptr<SubKeyReceiver> subkey_receiver_ = new SubKeyReceiver(); + + SubKeyReceiverCallback cb = + base::BindOnce(&SubKeyReceiver::OnSubKeysReceived, subkey_receiver_); + + // Make sure the rules will not be loaded in the StartRegionSubKeysRequest + // call. + requester_->ShouldLoadRules(false); + + // Start the normalization. + requester_->StartRegionSubKeysRequest(kLocale, 0, std::move(cb)); + + // Let the timeout execute. + base::RunLoop().RunUntilIdle(); + + // Since the rules are never loaded and the timeout is 0, the delegate should + // get notified that the subkeys could not be received. + EXPECT_EQ(subkey_receiver_->subkeys_size(), kEmptySize); +} + +// Tests that if the rules are not loaded before the call to +// StartRegionSubKeysRequest, they will be loaded in the call. +TEST_F(SubKeyRequesterTest, StartRequest_RulesNotLoaded_WillLoad) { + scoped_refptr<SubKeyReceiver> subkey_receiver_ = new SubKeyReceiver(); + + SubKeyReceiverCallback cb = + base::BindOnce(&SubKeyReceiver::OnSubKeysReceived, subkey_receiver_); + + // Make sure the rules will not be loaded in the StartRegionSubKeysRequest + // call. + requester_->ShouldLoadRules(true); + // Start the request. + requester_->StartRegionSubKeysRequest(kLocale, 0, std::move(cb)); + + // Even if the rules are not loaded before the call to + // StartRegionSubKeysRequest, they should get loaded in the call. Since our + // test source is synchronous, the request will happen synchronously + // too. + EXPECT_TRUE(requester_->AreRulesLoadedForRegion(kLocale)); + EXPECT_EQ(subkey_receiver_->subkeys_size(), kCorrectSize); +} + +} // namespace payments diff --git a/chromium/components/payments/core/test_address_normalizer.cc b/chromium/components/payments/core/test_address_normalizer.cc new file mode 100644 index 00000000000..e0b9fc868e8 --- /dev/null +++ b/chromium/components/payments/core/test_address_normalizer.cc @@ -0,0 +1,38 @@ +// Copyright 2017 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/payments/core/test_address_normalizer.h" + +namespace payments { + +bool TestAddressNormalizer::AreRulesLoadedForRegion( + const std::string& region_code) { + return true; +} + +void TestAddressNormalizer::StartAddressNormalization( + const autofill::AutofillProfile& profile, + const std::string& region_code, + int timeout_seconds, + AddressNormalizer::Delegate* requester) { + if (instantaneous_normalization_) { + requester->OnAddressNormalized(profile); + return; + } + + // Setup the necessary variables for the delayed normalization. + profile_ = profile; + requester_ = requester; +} + +void TestAddressNormalizer::DelayNormalization() { + instantaneous_normalization_ = false; +} + +void TestAddressNormalizer::CompleteAddressNormalization() { + DCHECK(instantaneous_normalization_ == false); + requester_->OnAddressNormalized(profile_); +} + +} // namespace payments diff --git a/chromium/components/payments/core/test_address_normalizer.h b/chromium/components/payments/core/test_address_normalizer.h new file mode 100644 index 00000000000..56def89de36 --- /dev/null +++ b/chromium/components/payments/core/test_address_normalizer.h @@ -0,0 +1,46 @@ +// Copyright 2017 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_PAYMENTS_CORE_TEST_ADDRESS_NORMALIZER_H_ +#define COMPONENTS_PAYMENTS_CORE_TEST_ADDRESS_NORMALIZER_H_ + +#include "components/payments/core/address_normalizer.h" + +#include "components/autofill/core/browser/autofill_profile.h" + +namespace payments { + +// A simpler version of the address normalizer to be used in tests. Can be set +// to normalize instantaneously or to wait for a call. +class TestAddressNormalizer : public AddressNormalizer { + public: + TestAddressNormalizer() {} + + void LoadRulesForRegion(const std::string& region_code) override {} + + bool AreRulesLoadedForRegion(const std::string& region_code) override; + + void StartAddressNormalization( + const autofill::AutofillProfile& profile, + const std::string& region_code, + int timeout_seconds, + AddressNormalizer::Delegate* requester) override; + + void OnAddressValidationRulesLoaded(const std::string& region_code, + bool success) override {} + + void DelayNormalization(); + + void CompleteAddressNormalization(); + + private: + autofill::AutofillProfile profile_; + AddressNormalizer::Delegate* requester_; + + bool instantaneous_normalization_ = true; +}; + +} // namespace payments + +#endif // COMPONENTS_PAYMENTS_CORE_TEST_ADDRESS_NORMALIZER_H_
\ No newline at end of file diff --git a/chromium/components/payments/core/test_payment_request_delegate.cc b/chromium/components/payments/core/test_payment_request_delegate.cc new file mode 100644 index 00000000000..66794195362 --- /dev/null +++ b/chromium/components/payments/core/test_payment_request_delegate.cc @@ -0,0 +1,88 @@ +// Copyright 2017 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/payments/core/test_payment_request_delegate.h" + +#include "base/strings/utf_string_conversions.h" + +namespace payments { + +TestPaymentRequestDelegate::TestPaymentRequestDelegate( + autofill::PersonalDataManager* personal_data_manager) + : personal_data_manager_(personal_data_manager), + locale_("en-US"), + last_committed_url_("https://shop.com") {} + +TestPaymentRequestDelegate::~TestPaymentRequestDelegate() {} + +autofill::PersonalDataManager* +TestPaymentRequestDelegate::GetPersonalDataManager() { + return personal_data_manager_; +} + +const std::string& TestPaymentRequestDelegate::GetApplicationLocale() const { + return locale_; +} + +bool TestPaymentRequestDelegate::IsIncognito() const { + return false; +} + +bool TestPaymentRequestDelegate::IsSslCertificateValid() { + return true; +} + +const GURL& TestPaymentRequestDelegate::GetLastCommittedURL() const { + return last_committed_url_; +} + +void TestPaymentRequestDelegate::DoFullCardRequest( + const autofill::CreditCard& credit_card, + base::WeakPtr<autofill::payments::FullCardRequest::ResultDelegate> + result_delegate) { + if (instantaneous_full_card_request_result_) { + result_delegate->OnFullCardRequestSucceeded(credit_card, + base::ASCIIToUTF16("123")); + return; + } + + full_card_request_card_ = credit_card; + full_card_result_delegate_ = result_delegate; +} + +AddressNormalizer* TestPaymentRequestDelegate::GetAddressNormalizer() { + return &address_normalizer_; +} + +autofill::RegionDataLoader* TestPaymentRequestDelegate::GetRegionDataLoader() { + return nullptr; +} + +ukm::UkmRecorder* TestPaymentRequestDelegate::GetUkmRecorder() { + return nullptr; +} + +TestAddressNormalizer* TestPaymentRequestDelegate::test_address_normalizer() { + return &address_normalizer_; +} + +void TestPaymentRequestDelegate::DelayFullCardRequestCompletion() { + instantaneous_full_card_request_result_ = false; +} + +void TestPaymentRequestDelegate::CompleteFullCardRequest() { + DCHECK(instantaneous_full_card_request_result_ == false); + full_card_result_delegate_->OnFullCardRequestSucceeded( + full_card_request_card_, base::ASCIIToUTF16("123")); +} + +std::string TestPaymentRequestDelegate::GetAuthenticatedEmail() const { + return ""; +} + +PrefService* TestPaymentRequestDelegate::GetPrefService() { + return nullptr; +} + +} // namespace payments diff --git a/chromium/components/payments/core/test_payment_request_delegate.h b/chromium/components/payments/core/test_payment_request_delegate.h new file mode 100644 index 00000000000..e0697a7d326 --- /dev/null +++ b/chromium/components/payments/core/test_payment_request_delegate.h @@ -0,0 +1,59 @@ +// Copyright 2017 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_PAYMENTS_CORE_TEST_PAYMENT_REQUEST_DELEGATE_H_ +#define COMPONENTS_PAYMENTS_CORE_TEST_PAYMENT_REQUEST_DELEGATE_H_ + +#include <string> + +#include "components/payments/core/payment_request_delegate.h" +#include "components/payments/core/test_address_normalizer.h" + +namespace payments { + +class TestPaymentRequestDelegate : public PaymentRequestDelegate { + public: + TestPaymentRequestDelegate( + autofill::PersonalDataManager* personal_data_manager); + ~TestPaymentRequestDelegate() override; + + // PaymentRequestDelegate + void ShowDialog(PaymentRequest* request) override {} + void CloseDialog() override {} + void ShowErrorMessage() override {} + autofill::PersonalDataManager* GetPersonalDataManager() override; + const std::string& GetApplicationLocale() const override; + bool IsIncognito() const override; + bool IsSslCertificateValid() override; + const GURL& GetLastCommittedURL() const override; + void DoFullCardRequest( + const autofill::CreditCard& credit_card, + base::WeakPtr<autofill::payments::FullCardRequest::ResultDelegate> + result_delegate) override; + AddressNormalizer* GetAddressNormalizer() override; + autofill::RegionDataLoader* GetRegionDataLoader() override; + ukm::UkmRecorder* GetUkmRecorder() override; + std::string GetAuthenticatedEmail() const override; + PrefService* GetPrefService() override; + + TestAddressNormalizer* test_address_normalizer(); + void DelayFullCardRequestCompletion(); + void CompleteFullCardRequest(); + + private: + autofill::PersonalDataManager* personal_data_manager_; + std::string locale_; + const GURL last_committed_url_; + TestAddressNormalizer address_normalizer_; + + bool instantaneous_full_card_request_result_ = true; + autofill::CreditCard full_card_request_card_; + base::WeakPtr<autofill::payments::FullCardRequest::ResultDelegate> + full_card_result_delegate_; + DISALLOW_COPY_AND_ASSIGN(TestPaymentRequestDelegate); +}; + +} // namespace payments + +#endif // COMPONENTS_PAYMENTS_CORE_TEST_PAYMENT_REQUEST_DELEGATE_H_ |