Commit fef3d922 authored by Sahel Sharify's avatar Sahel Sharify Committed by Commit Bot

[Payments] Enable shipping and contact info delegation [2/5]

With this changes the payment sheet view won't show shipping/contact
sections if the selected instrument supports shipping/contact delegation.

In this case the pay button will be enabled when the selected
instrument handles the requested payment options regardless of whether
or not autofill data is available.

For overall flow please
check https://chromium-review.googlesource.com/c/chromium/src/+/1779003

Bug: 984694
Change-Id: Ieedbc79b92b5991d03867da98d04ccbc10f2f0c7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1793785
Commit-Queue: Sahel Sharify <sahel@chromium.org>
Reviewed-by: default avatarRouslan Solomakhin <rouslan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#695440}
parent 0ccefbcd
......@@ -438,7 +438,7 @@ void PaymentSheetViewController::FillContentView(views::View* content_view) {
layout->StartRow(views::GridLayout::kFixedSize, 0);
layout->AddView(std::move(summary_row));
if (spec()->request_shipping()) {
if (state()->ShouldShowShippingSection()) {
std::unique_ptr<PaymentRequestRowView> shipping_row = CreateShippingRow();
shipping_row->set_previous_row(previous_row->AsWeakPtr());
previous_row = shipping_row.get();
......@@ -462,8 +462,7 @@ void PaymentSheetViewController::FillContentView(views::View* content_view) {
previous_row = payment_method_row.get();
layout->StartRow(views::GridLayout::kFixedSize, 0);
layout->AddView(std::move(payment_method_row));
if (spec()->request_payer_name() || spec()->request_payer_email() ||
spec()->request_payer_phone()) {
if (state()->ShouldShowContactSection()) {
std::unique_ptr<PaymentRequestRowView> contact_info_row =
CreateContactInfoRow();
contact_info_row->set_previous_row(previous_row->AsWeakPtr());
......
......@@ -9,6 +9,7 @@
#include "components/autofill/core/browser/data_model/autofill_profile.h"
#include "components/autofill/core/browser/data_model/credit_card.h"
#include "components/autofill/core/browser/field_types.h"
#include "components/autofill/core/browser/personal_data_manager.h"
#include "components/strings/grit/components_strings.h"
#include "testing/gtest/include/gtest/gtest.h"
......@@ -153,6 +154,154 @@ IN_PROC_BROWSER_TEST_F(PaymentSheetViewControllerContactDetailsTest,
EXPECT_FALSE(IsPayButtonEnabled());
}
// Payment sheet view skips showing shipping section when the selected
// instrument supports shipping delegation, the pay button is enabled with blank
// autofill data.
IN_PROC_BROWSER_TEST_F(PaymentSheetViewControllerContactDetailsTest,
ShippingDelegation) {
// Install a payment handler which supports shipping delegation.
NavigateTo("/payment_handler.html");
std::string expected = "success";
EXPECT_EQ(expected, content::EvalJs(GetActiveWebContents(), "install()"));
EXPECT_EQ(expected,
content::EvalJs(GetActiveWebContents(),
"enableDelegations(['shippingAddress'])"));
// Invoke a payment request with basic-card and methodName =
// window.location.origin + '/pay' supportedMethods (see payment_handler.js).
ResetEventWaiterForDialogOpened();
EXPECT_EQ(
expected,
content::EvalJs(GetActiveWebContents(),
"paymentRequestWithOptions({requestShipping: true})"));
WaitForObservedEvent();
// Verify that no autofill profile exists.
EXPECT_TRUE(GetDataManager()->GetProfiles().empty());
// Shipping address and shipping option sections are not shown in the payment
// sheet view since handling shipping address is delegated to the selected
// payment handler (payment_handler.js).
EXPECT_EQ(nullptr,
dialog_view()->GetViewByID(static_cast<int>(
DialogViewID::PAYMENT_SHEET_SHIPPING_ADDRESS_SECTION_BUTTON)));
EXPECT_EQ(nullptr, dialog_view()->GetViewByID(static_cast<int>(
DialogViewID::PAYMENT_SHEET_SHIPPING_OPTION_SECTION)));
// Payment button should be enabled with blank autofill profiles since the
// payment handler supports shipping delegation.
EXPECT_TRUE(IsPayButtonEnabled());
}
// Payment sheet view skips showing contact section when the selected instrument
// supports contact delegation.
IN_PROC_BROWSER_TEST_F(PaymentSheetViewControllerContactDetailsTest,
ContactDelegation) {
// Install a payment handler which supports contact delegation.
NavigateTo("/payment_handler.html");
std::string expected = "success";
EXPECT_EQ(expected, content::EvalJs(GetActiveWebContents(), "install()"));
EXPECT_EQ(
expected,
content::EvalJs(
GetActiveWebContents(),
"enableDelegations(['payerName', 'payerPhone', 'payerEmail'])"));
// Invoke a payment request with basic-card and methodName =
// window.location.origin + '/pay' supportedMethods (see payment_handler.js).
ResetEventWaiterForDialogOpened();
EXPECT_EQ(
expected,
content::EvalJs(GetActiveWebContents(),
"paymentRequestWithOptions({requestPayerName: true, "
"requestPayerPhone: true, requestPayerEmail: true})"));
WaitForObservedEvent();
// Verify that no autofill profile exists.
EXPECT_TRUE(GetDataManager()->GetProfiles().empty());
// Contact info section is not shown in the payment sheet view since handling
// required contact information is delegated to the selected payment handler
// (payment_handler.js).
EXPECT_EQ(nullptr,
dialog_view()->GetViewByID(static_cast<int>(
DialogViewID::PAYMENT_SHEET_CONTACT_INFO_SECTION_BUTTON)));
// Payment button should be enabled with blank autofill profiles since the
// payment handler supports contact delegation.
EXPECT_TRUE(IsPayButtonEnabled());
}
// Payment sheet view shows shipping section when the selected instrument
// supports contact delegation only.
IN_PROC_BROWSER_TEST_F(PaymentSheetViewControllerContactDetailsTest,
ContactOnlyDelegationShippingRequested) {
// Install a payment handler which supports contact delegation.
NavigateTo("/payment_handler.html");
std::string expected = "success";
EXPECT_EQ(expected, content::EvalJs(GetActiveWebContents(), "install()"));
EXPECT_EQ(
expected,
content::EvalJs(
GetActiveWebContents(),
"enableDelegations(['payerName', 'payerPhone', 'payerEmail'])"));
// Invoke a payment request with basic-card and methodName =
// window.location.origin + '/pay' supportedMethods (see payment_handler.js).
ResetEventWaiterForDialogOpened();
EXPECT_EQ(
expected,
content::EvalJs(GetActiveWebContents(),
"paymentRequestWithOptions({requestShipping: true})"));
WaitForObservedEvent();
// Verify that no autofill profile exists.
EXPECT_TRUE(GetDataManager()->GetProfiles().empty());
// Shipping section is still shown since the selected payment instrument does
// not support delegation of shipping address.
EXPECT_NE(nullptr,
dialog_view()->GetViewByID(static_cast<int>(
DialogViewID::PAYMENT_SHEET_SHIPPING_ADDRESS_SECTION_BUTTON)));
// Payment button should be disabled since the browser should collect shipping
// address.
EXPECT_FALSE(IsPayButtonEnabled());
}
// Payment sheet view shows contact section when the selected instrument does
// not support delegation of all required contact details.
IN_PROC_BROWSER_TEST_F(PaymentSheetViewControllerContactDetailsTest,
PartialContactDelegation) {
// Install a payment handler which supports delegation of all required contact
// information except payer's email.
NavigateTo("/payment_handler.html");
std::string expected = "success";
EXPECT_EQ(expected, content::EvalJs(GetActiveWebContents(), "install()"));
EXPECT_EQ(expected,
content::EvalJs(GetActiveWebContents(),
"enableDelegations(['payerName', 'payerPhone'])"));
// Invoke a payment request with basic-card and methodName =
// window.location.origin + '/pay' supportedMethods (see payment_handler.js).
ResetEventWaiterForDialogOpened();
EXPECT_EQ(
expected,
content::EvalJs(GetActiveWebContents(),
"paymentRequestWithOptions({requestPayerName: true, "
"requestPayerPhone: true, requestPayerEmail: true})"));
WaitForObservedEvent();
// Verify that no autofill profile exists.
EXPECT_TRUE(GetDataManager()->GetProfiles().empty());
// Contact info section is still shown since the selected payment instrument
// does not support delegation of all required contact info.
EXPECT_NE(nullptr,
dialog_view()->GetViewByID(static_cast<int>(
DialogViewID::PAYMENT_SHEET_CONTACT_INFO_SECTION_BUTTON)));
// Payment button should be disabled since the browser should collect payer's
// email.
EXPECT_FALSE(IsPayButtonEnabled());
}
// If shipping and contact info are requested, show all the rows.
IN_PROC_BROWSER_TEST_F(PaymentSheetViewControllerContactDetailsTest,
AllRowsPresent) {
......
......@@ -537,8 +537,14 @@ bool PaymentRequest::SatisfiesSkipUIConstraints() {
is_show_user_gesture_ && state()->IsInitialized() &&
spec()->IsInitialized() && state()->available_instruments().size() == 1 &&
spec()->stringified_method_data().size() == 1 &&
!spec()->request_shipping() && !spec()->request_payer_name() &&
!spec()->request_payer_phone() && !spec()->request_payer_email();
(!spec()->request_shipping() ||
state()->available_instruments().front()->HandlesShippingAddress()) &&
(!spec()->request_payer_name() ||
state()->available_instruments().front()->HandlesPayerName()) &&
(!spec()->request_payer_phone() ||
state()->available_instruments().front()->HandlesPayerPhone()) &&
(!spec()->request_payer_email() ||
state()->available_instruments().front()->HandlesPayerEmail());
if (skipped_payment_request_ui_) {
DCHECK(state()->IsInitialized() && spec()->IsInitialized());
journey_logger_.SetEventOccurred(JourneyLogger::EVENT_SKIPPED_SHOW);
......
......@@ -330,19 +330,18 @@ void PaymentRequestState::OnPaymentAppWindowClosed() {
}
void PaymentRequestState::RecordUseStats() {
if (spec_->request_shipping()) {
if (ShouldShowShippingSection()) {
DCHECK(selected_shipping_profile_);
personal_data_manager_->RecordUseOf(*selected_shipping_profile_);
}
if (spec_->request_payer_name() || spec_->request_payer_email() ||
spec_->request_payer_phone()) {
if (ShouldShowContactSection()) {
DCHECK(selected_contact_profile_);
// If the same address was used for both contact and shipping, the stats
// should only be updated once.
if (!spec_->request_shipping() || (selected_shipping_profile_->guid() !=
selected_contact_profile_->guid())) {
if (!ShouldShowShippingSection() || (selected_shipping_profile_->guid() !=
selected_contact_profile_->guid())) {
personal_data_manager_->RecordUseOf(*selected_contact_profile_);
}
}
......@@ -534,6 +533,31 @@ void PaymentRequestState::SelectDefaultShippingAddressAndNotifyObservers() {
UpdateIsReadyToPayAndNotifyObservers();
}
bool PaymentRequestState::ShouldShowShippingSection() const {
if (!spec_->request_shipping())
return false;
return selected_instrument_ ? !selected_instrument_->HandlesShippingAddress()
: true;
}
bool PaymentRequestState::ShouldShowContactSection() const {
if (spec_->request_payer_name() &&
(!selected_instrument_ || !selected_instrument_->HandlesPayerName())) {
return true;
}
if (spec_->request_payer_email() &&
(!selected_instrument_ || !selected_instrument_->HandlesPayerEmail())) {
return true;
}
if (spec_->request_payer_phone() &&
(!selected_instrument_ || !selected_instrument_->HandlesPayerPhone())) {
return true;
}
return false;
}
base::WeakPtr<PaymentRequestState> PaymentRequestState::AsWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
......@@ -561,8 +585,7 @@ void PaymentRequestState::PopulateProfileCache() {
// Set the number of suggestions shown for the sections requested by the
// merchant.
if (spec_->request_payer_name() || spec_->request_payer_phone() ||
spec_->request_payer_email()) {
if (ShouldShowContactSection()) {
bool has_complete_contact =
contact_profiles_.empty()
? false
......@@ -572,7 +595,7 @@ void PaymentRequestState::PopulateProfileCache() {
JourneyLogger::Section::SECTION_CONTACT_INFO, contact_profiles_.size(),
has_complete_contact);
}
if (spec_->request_shipping()) {
if (ShouldShowShippingSection()) {
bool has_complete_shipping =
shipping_profiles_.empty()
? false
......@@ -668,13 +691,18 @@ bool PaymentRequestState::ArePaymentOptionsSatisfied() {
if (is_waiting_for_merchant_validation_)
return false;
if (!profile_comparator()->IsShippingComplete(selected_shipping_profile_))
if (ShouldShowShippingSection() &&
(!spec_->selected_shipping_option() ||
!profile_comparator()->IsShippingComplete(selected_shipping_profile_))) {
return false;
}
if (spec_->request_shipping() && !spec_->selected_shipping_option())
if (ShouldShowContactSection() &&
!profile_comparator()->IsContactInfoComplete(selected_contact_profile_)) {
return false;
}
return profile_comparator()->IsContactInfoComplete(selected_contact_profile_);
return true;
}
void PaymentRequestState::OnAddressNormalized(
......
......@@ -264,6 +264,14 @@ class PaymentRequestState : public PaymentResponseHelper::Delegate,
// Selects the default shipping address.
void SelectDefaultShippingAddressAndNotifyObservers();
// Returns true when shipping address is required and the selected instrument
// (if any) does not support shipping address delegation.
bool ShouldShowShippingSection() const;
// Returns true when payer name/phone/email is required and the selected
// instrument (if any) does not support required contact info delegation.
bool ShouldShowContactSection() const;
base::WeakPtr<PaymentRequestState> AsWeakPtr();
void set_is_show_user_gesture(bool is_show_user_gesture) {
......
......@@ -481,15 +481,6 @@ void JourneyLogger::ValidateEventBits() const {
if (events_ & EVENT_SKIPPED_SHOW) {
// Built in autofill payment handler for basic card should not skip UI show.
DCHECK(!(events_ & EVENT_SELECTED_CREDIT_CARD));
// Payment sheet should not get skipped when any of the following info is
// required, unless skip-to-gpay is enabled. Checking the feature flag here
// may mess up the Finch result, so using EVENT_REQUEST_METHOD_GOOGLE as a
// proxy.
bool gpay_requested = events_ & EVENT_REQUEST_METHOD_GOOGLE;
DCHECK(!(events_ & EVENT_REQUEST_SHIPPING) || gpay_requested);
DCHECK(!(events_ & EVENT_REQUEST_PAYER_NAME) || gpay_requested);
DCHECK(!(events_ & EVENT_REQUEST_PAYER_EMAIL) || gpay_requested);
DCHECK(!(events_ & EVENT_REQUEST_PAYER_PHONE) || gpay_requested);
}
// Check that the two bits are not set at the same time.
......
......@@ -74,3 +74,45 @@ async function launch() { // eslint-disable-line no-unused-vars
return e.toString();
}
}
/**
* Creates a payment request with required information and calls request.show()
* to invoke payment sheet UI. To ensure that UI gets shown two payment methods
* are supported: One url-based and one 'basic-card'.
* @param {Object} options The list of requested paymentOptions.
* @return {string} The 'success' or error message.
*/
function paymentRequestWithOptions(options) { // eslint-disable-line no-unused-vars, max-len
try {
const request = new PaymentRequest([{
supportedMethods: methodName,
},
{
supportedMethods: 'basic-card',
},
], {
total: {
label: 'Total',
amount: {
currency: 'USD',
value: '0.01',
},
},
shippingOptions: [{
id: 'freeShippingOption',
label: 'Free global shipping',
amount: {
currency: 'USD',
value: '0',
},
selected: true,
}],
},
options);
request.show();
return 'success';
} catch (e) {
return e.toString();
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment