Index: components/payments/core/payments_profile_comparator.cc |
diff --git a/components/payments/core/payments_profile_comparator.cc b/components/payments/core/payments_profile_comparator.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..832e005dd9d2494fcd28cba5033aa4daef1f8d73 |
--- /dev/null |
+++ b/components/payments/core/payments_profile_comparator.cc |
@@ -0,0 +1,251 @@ |
+// 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 { |
+ if (!profile) |
+ return 0; |
+ |
+ // 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()); |
+} |
+ |
+bool PaymentsProfileComparator::IsContactMoreComplete( |
+ const autofill::AutofillProfile* p1, |
+ const autofill::AutofillProfile* p2) const { |
+ return GetContactCompletenessScore(p1) > GetContactCompletenessScore(p2); |
+} |
+ |
+base::string16 PaymentsProfileComparator::GetStringForMissingContactFields( |
+ const autofill::AutofillProfile& profile) const { |
+ return GetStringForMissingFields(GetMissingProfileFields(&profile) & |
+ GetRequiredProfileFieldsForContact()); |
+} |
+ |
+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()); |
+ if (!autofill::IsValidPhoneNumber(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); |
+} |
+ |
+} // namespace payments |