Commit 968ae4da authored by Anthony Vallee-Dubois's avatar Anthony Vallee-Dubois Committed by Commit Bot

[Web Payments] Implement skip UI flow on desktop

Bug: 820161
Change-Id: I5a458a374890a285daa594b3c2ca72a821d2f93a
Reviewed-on: https://chromium-review.googlesource.com/955891Reviewed-by: default avatarGanggui Tang <gogerald@chromium.org>
Reviewed-by: default avatarRouslan Solomakhin <rouslan@chromium.org>
Commit-Queue: anthonyvd <anthonyvd@chromium.org>
Cr-Commit-Position: refs/heads/master@{#543405}
parent b63cda6a
...@@ -2735,6 +2735,11 @@ const FeatureEntry kFeatureEntries[] = { ...@@ -2735,6 +2735,11 @@ const FeatureEntry kFeatureEntries[] = {
flag_descriptions::kServiceWorkerPaymentAppsDescription, flag_descriptions::kServiceWorkerPaymentAppsDescription,
kOsAndroid | kOsDesktop, kOsAndroid | kOsDesktop,
FEATURE_VALUE_TYPE(features::kServiceWorkerPaymentApps)}, FEATURE_VALUE_TYPE(features::kServiceWorkerPaymentApps)},
{"enable-web-payments-single-app-ui-skip",
flag_descriptions::kEnableWebPaymentsSingleAppUiSkipName,
flag_descriptions::kEnableWebPaymentsSingleAppUiSkipDescription,
kOsAndroid | kOsDesktop,
FEATURE_VALUE_TYPE(payments::features::kWebPaymentsSingleAppUiSkip)},
#if defined(OS_ANDROID) #if defined(OS_ANDROID)
{"enable-android-pay-integration-v1", {"enable-android-pay-integration-v1",
flag_descriptions::kEnableAndroidPayIntegrationV1Name, flag_descriptions::kEnableAndroidPayIntegrationV1Name,
...@@ -2749,11 +2754,6 @@ const FeatureEntry kFeatureEntries[] = { ...@@ -2749,11 +2754,6 @@ const FeatureEntry kFeatureEntries[] = {
flag_descriptions::kEnableWebPaymentsMethodSectionOrderV2Description, flag_descriptions::kEnableWebPaymentsMethodSectionOrderV2Description,
kOsAndroid, kOsAndroid,
FEATURE_VALUE_TYPE(payments::features::kWebPaymentsMethodSectionOrderV2)}, FEATURE_VALUE_TYPE(payments::features::kWebPaymentsMethodSectionOrderV2)},
{"enable-web-payments-single-app-ui-skip",
flag_descriptions::kEnableWebPaymentsSingleAppUiSkipName,
flag_descriptions::kEnableWebPaymentsSingleAppUiSkipDescription,
kOsAndroid,
FEATURE_VALUE_TYPE(chrome::android::kWebPaymentsSingleAppUiSkip)},
{"android-payment-apps", flag_descriptions::kAndroidPaymentAppsName, {"android-payment-apps", flag_descriptions::kAndroidPaymentAppsName,
flag_descriptions::kAndroidPaymentAppsDescription, kOsAndroid, flag_descriptions::kAndroidPaymentAppsDescription, kOsAndroid,
FEATURE_VALUE_TYPE(chrome::android::kAndroidPaymentApps)}, FEATURE_VALUE_TYPE(chrome::android::kAndroidPaymentApps)},
......
...@@ -130,7 +130,7 @@ const base::Feature* kFeaturesExposedToJava[] = { ...@@ -130,7 +130,7 @@ const base::Feature* kFeaturesExposedToJava[] = {
&kVrIconInDaydreamHome, &kVrIconInDaydreamHome,
&payments::features::kWebPaymentsMethodSectionOrderV2, &payments::features::kWebPaymentsMethodSectionOrderV2,
&payments::features::kWebPaymentsModifiers, &payments::features::kWebPaymentsModifiers,
&kWebPaymentsSingleAppUiSkip, &payments::features::kWebPaymentsSingleAppUiSkip,
&kWebVrAutopresentFromIntent, &kWebVrAutopresentFromIntent,
&kWebVrCardboardSupport, &kWebVrCardboardSupport,
&ntp_snippets::kArticleSuggestionsExpandableHeader, &ntp_snippets::kArticleSuggestionsExpandableHeader,
...@@ -387,9 +387,6 @@ const base::Feature kVrBrowsingNativeAndroidUi{ ...@@ -387,9 +387,6 @@ const base::Feature kVrBrowsingNativeAndroidUi{
const base::Feature kVrIconInDaydreamHome{"VrIconInDaydreamHome", const base::Feature kVrIconInDaydreamHome{"VrIconInDaydreamHome",
base::FEATURE_ENABLED_BY_DEFAULT}; base::FEATURE_ENABLED_BY_DEFAULT};
const base::Feature kWebPaymentsSingleAppUiSkip{
"WebPaymentsSingleAppUiSkip", base::FEATURE_ENABLED_BY_DEFAULT};
const base::Feature kWebVrAutopresentFromIntent{ const base::Feature kWebVrAutopresentFromIntent{
"WebVrAutopresentFromIntent", base::FEATURE_ENABLED_BY_DEFAULT}; "WebVrAutopresentFromIntent", base::FEATURE_ENABLED_BY_DEFAULT};
......
...@@ -85,7 +85,6 @@ extern const base::Feature kVrBrowsingFeedback; ...@@ -85,7 +85,6 @@ extern const base::Feature kVrBrowsingFeedback;
extern const base::Feature kVrBrowsingInCustomTab; extern const base::Feature kVrBrowsingInCustomTab;
extern const base::Feature kVrBrowsingNativeAndroidUi; extern const base::Feature kVrBrowsingNativeAndroidUi;
extern const base::Feature kVrIconInDaydreamHome; extern const base::Feature kVrIconInDaydreamHome;
extern const base::Feature kWebPaymentsSingleAppUiSkip;
extern const base::Feature kWebVrAutopresentFromIntent; extern const base::Feature kWebVrAutopresentFromIntent;
extern const base::Feature kWebVrCardboardSupport; extern const base::Feature kWebVrCardboardSupport;
......
...@@ -272,6 +272,12 @@ const char kEnableAsmWasmDescription[] = ...@@ -272,6 +272,12 @@ const char kEnableAsmWasmDescription[] =
R"*(Validate Asm.js when "use asm" is present and then convert to )*" R"*(Validate Asm.js when "use asm" is present and then convert to )*"
R"*(WebAssembly.)*"; R"*(WebAssembly.)*";
const char kEnableWebPaymentsSingleAppUiSkipName[] =
"Enable Web Payments single app UI skip";
const char kEnableWebPaymentsSingleAppUiSkipDescription[] =
"Enable Web Payments to skip showing its UI if the developer specifies a "
"single app.";
const char kEnableAutofillCreditCardAblationExperimentDisplayName[] = const char kEnableAutofillCreditCardAblationExperimentDisplayName[] =
"Credit card autofill ablation experiment."; "Credit card autofill ablation experiment.";
const char kEnableAutofillCreditCardAblationExperimentDescription[] = const char kEnableAutofillCreditCardAblationExperimentDescription[] =
...@@ -2041,12 +2047,6 @@ const char kEnableWebPaymentsMethodSectionOrderV2Description[] = ...@@ -2041,12 +2047,6 @@ const char kEnableWebPaymentsMethodSectionOrderV2Description[] =
"Enable this option to display payment method section above address " "Enable this option to display payment method section above address "
"section instead of below it."; "section instead of below it.";
const char kEnableWebPaymentsSingleAppUiSkipName[] =
"Enable Web Payments single app UI skip";
const char kEnableWebPaymentsSingleAppUiSkipDescription[] =
"Enable Web Payments to skip showing its UI if the developer specifies a "
"single app.";
const char kGrantNotificationsToDSEName[] = const char kGrantNotificationsToDSEName[] =
"Grant notifications to the Default Search Engine"; "Grant notifications to the Default Search Engine";
const char kGrantNotificationsToDSENameDescription[] = const char kGrantNotificationsToDSENameDescription[] =
......
...@@ -394,6 +394,9 @@ extern const char kEnableSharedArrayBufferDescription[]; ...@@ -394,6 +394,9 @@ extern const char kEnableSharedArrayBufferDescription[];
extern const char kEnableWasmName[]; extern const char kEnableWasmName[];
extern const char kEnableWasmDescription[]; extern const char kEnableWasmDescription[];
extern const char kEnableWebPaymentsSingleAppUiSkipName[];
extern const char kEnableWebPaymentsSingleAppUiSkipDescription[];
extern const char kEnableWebUsbName[]; extern const char kEnableWebUsbName[];
extern const char kEnableWebUsbDescription[]; extern const char kEnableWebUsbDescription[];
...@@ -1239,9 +1242,6 @@ extern const char kEnableWebNfcDescription[]; ...@@ -1239,9 +1242,6 @@ extern const char kEnableWebNfcDescription[];
extern const char kEnableWebPaymentsMethodSectionOrderV2Name[]; extern const char kEnableWebPaymentsMethodSectionOrderV2Name[];
extern const char kEnableWebPaymentsMethodSectionOrderV2Description[]; extern const char kEnableWebPaymentsMethodSectionOrderV2Description[];
extern const char kEnableWebPaymentsSingleAppUiSkipName[];
extern const char kEnableWebPaymentsSingleAppUiSkipDescription[];
extern const char kGrantNotificationsToDSEName[]; extern const char kGrantNotificationsToDSEName[];
extern const char kGrantNotificationsToDSENameDescription[]; extern const char kGrantNotificationsToDSENameDescription[];
......
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
#include "components/autofill/core/browser/region_data_loader_impl.h" #include "components/autofill/core/browser/region_data_loader_impl.h"
#include "components/keyed_service/core/service_access_type.h" #include "components/keyed_service/core/service_access_type.h"
#include "components/payments/content/payment_manifest_web_data_service.h" #include "components/payments/content/payment_manifest_web_data_service.h"
#include "components/payments/content/payment_request.h"
#include "components/payments/content/payment_request_dialog.h" #include "components/payments/content/payment_request_dialog.h"
#include "components/payments/core/payment_prefs.h" #include "components/payments/core/payment_prefs.h"
#include "components/signin/core/browser/signin_manager.h" #include "components/signin/core/browser/signin_manager.h"
...@@ -53,31 +54,35 @@ std::unique_ptr<::i18n::addressinput::Storage> GetAddressInputStorage() { ...@@ -53,31 +54,35 @@ std::unique_ptr<::i18n::addressinput::Storage> GetAddressInputStorage() {
ChromePaymentRequestDelegate::ChromePaymentRequestDelegate( ChromePaymentRequestDelegate::ChromePaymentRequestDelegate(
content::WebContents* web_contents) content::WebContents* web_contents)
: dialog_(nullptr), web_contents_(web_contents) {} : shown_dialog_(nullptr), web_contents_(web_contents) {}
ChromePaymentRequestDelegate::~ChromePaymentRequestDelegate() {} ChromePaymentRequestDelegate::~ChromePaymentRequestDelegate() {}
void ChromePaymentRequestDelegate::ShowDialog(PaymentRequest* request) { void ChromePaymentRequestDelegate::ShowDialog(PaymentRequest* request) {
DCHECK_EQ(nullptr, dialog_); DCHECK_EQ(nullptr, shown_dialog_);
dialog_ = chrome::CreatePaymentRequestDialog(request); hidden_dialog_ = std::unique_ptr<PaymentRequestDialog>(
dialog_->ShowDialog(); chrome::CreatePaymentRequestDialog(request));
MaybeShowHiddenDialog(request);
} }
void ChromePaymentRequestDelegate::CloseDialog() { void ChromePaymentRequestDelegate::CloseDialog() {
if (dialog_) { if (shown_dialog_) {
dialog_->CloseDialog(); shown_dialog_->CloseDialog();
dialog_ = nullptr; shown_dialog_ = nullptr;
} }
if (hidden_dialog_)
hidden_dialog_.reset();
} }
void ChromePaymentRequestDelegate::ShowErrorMessage() { void ChromePaymentRequestDelegate::ShowErrorMessage() {
if (dialog_) if (shown_dialog_)
dialog_->ShowErrorMessage(); shown_dialog_->ShowErrorMessage();
} }
void ChromePaymentRequestDelegate::ShowProcessingSpinner() { void ChromePaymentRequestDelegate::ShowProcessingSpinner() {
if (dialog_) if (shown_dialog_)
dialog_->ShowProcessingSpinner(); shown_dialog_->ShowProcessingSpinner();
} }
autofill::PersonalDataManager* autofill::PersonalDataManager*
...@@ -111,7 +116,9 @@ void ChromePaymentRequestDelegate::DoFullCardRequest( ...@@ -111,7 +116,9 @@ void ChromePaymentRequestDelegate::DoFullCardRequest(
const autofill::CreditCard& credit_card, const autofill::CreditCard& credit_card,
base::WeakPtr<autofill::payments::FullCardRequest::ResultDelegate> base::WeakPtr<autofill::payments::FullCardRequest::ResultDelegate>
result_delegate) { result_delegate) {
dialog_->ShowCvcUnmaskPrompt(credit_card, result_delegate, web_contents_); if (shown_dialog_)
shown_dialog_->ShowCvcUnmaskPrompt(credit_card, result_delegate,
web_contents_);
} }
autofill::RegionDataLoader* autofill::RegionDataLoader*
...@@ -171,10 +178,26 @@ ChromePaymentRequestDelegate::GetDisplayManager() { ...@@ -171,10 +178,26 @@ ChromePaymentRequestDelegate::GetDisplayManager() {
void ChromePaymentRequestDelegate::EmbedPaymentHandlerWindow( void ChromePaymentRequestDelegate::EmbedPaymentHandlerWindow(
const GURL& url, const GURL& url,
PaymentHandlerOpenWindowCallback callback) { PaymentHandlerOpenWindowCallback callback) {
if (dialog_) if (hidden_dialog_) {
dialog_->ShowPaymentHandlerScreen(url, std::move(callback)); shown_dialog_ = hidden_dialog_.release();
else shown_dialog_->ShowDialogAtPaymentHandlerSheet(url, std::move(callback));
std::move(callback).Run(false, 0, 0); } else if (shown_dialog_) {
shown_dialog_->ShowPaymentHandlerScreen(url, std::move(callback));
} else {
std::move(callback).Run(/*success=*/false,
/*render_process_id=*/0,
/*render_frame_id=*/0);
}
}
void ChromePaymentRequestDelegate::MaybeShowHiddenDialog(
PaymentRequest* request) {
if (request->SatisfiesSkipUIConstraints()) {
request->Pay();
} else {
shown_dialog_ = hidden_dialog_.release();
shown_dialog_->ShowDialog();
}
} }
} // namespace payments } // namespace payments
...@@ -54,8 +54,24 @@ class ChromePaymentRequestDelegate : public ContentPaymentRequestDelegate { ...@@ -54,8 +54,24 @@ class ChromePaymentRequestDelegate : public ContentPaymentRequestDelegate {
protected: protected:
// Reference to the dialog so that we can satisfy calls to CloseDialog(). This // Reference to the dialog so that we can satisfy calls to CloseDialog(). This
// reference is invalid once CloseDialog() has been called on it, because the // reference is invalid once CloseDialog() has been called on it, because the
// dialog will be destroyed. Protected for testing. // dialog will be destroyed. Owned by the views:: dialog machinery. Protected
PaymentRequestDialog* dialog_; // for testing.
PaymentRequestDialog* shown_dialog_;
// The instance of the dialog that was created but not shown yet. Since it
// hasn't been shown, it's still owned by it's creator. This is non null only
// when the current Payment Request supports skipping the payment sheet (see
// PaymentRequest::SatisfiesSkipUIConstraints) and is reset once the
// underlying pointer becomes owned by the views:: machinery (when the dialog
// is shown).
std::unique_ptr<PaymentRequestDialog> hidden_dialog_;
// Shows |hidden_dialog_| if the current Payment Request doesn't support the
// skip UI flow. This also transfer its ownership to the views dialog code and
// keep a reference to it in |shown_dialog_|.
// Otherwise, this calls Pay() on the current Payment Request to allow the
// skip UI flow to carry on.
void MaybeShowHiddenDialog(PaymentRequest* request);
private: private:
// Not owned but outlives the PaymentRequest object that owns this. // Not owned but outlives the PaymentRequest object that owns this.
......
...@@ -141,6 +141,19 @@ void PaymentRequestDialogView::ShowDialog() { ...@@ -141,6 +141,19 @@ void PaymentRequestDialogView::ShowDialog() {
constrained_window::ShowWebModalDialogViews(this, request_->web_contents()); constrained_window::ShowWebModalDialogViews(this, request_->web_contents());
} }
void PaymentRequestDialogView::ShowDialogAtPaymentHandlerSheet(
const GURL& url,
PaymentHandlerOpenWindowCallback callback) {
view_stack_->Push(CreateViewAndInstallController(
std::make_unique<PaymentHandlerWebFlowViewController>(
request_->spec(), request_->state(), this,
GetProfile(), url, std::move(callback)),
&controller_map_),
/* animate = */ false);
HideProcessingSpinner();
ShowDialog();
}
void PaymentRequestDialogView::CloseDialog() { void PaymentRequestDialogView::CloseDialog() {
// This calls PaymentRequestDialogView::Cancel() before closing. // This calls PaymentRequestDialogView::Cancel() before closing.
// ViewHierarchyChanged() also gets called after Cancel(). // ViewHierarchyChanged() also gets called after Cancel().
......
...@@ -106,6 +106,9 @@ class PaymentRequestDialogView : public views::DialogDelegateView, ...@@ -106,6 +106,9 @@ class PaymentRequestDialogView : public views::DialogDelegateView,
// payments::PaymentRequestDialog: // payments::PaymentRequestDialog:
void ShowDialog() override; void ShowDialog() override;
void ShowDialogAtPaymentHandlerSheet(
const GURL& url,
PaymentHandlerOpenWindowCallback callback) override;
void CloseDialog() override; void CloseDialog() override;
void ShowErrorMessage() override; void ShowErrorMessage() override;
void ShowProcessingSpinner() override; void ShowProcessingSpinner() override;
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
#include "base/macros.h" #include "base/macros.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h" #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/permissions/permission_request_manager.h" #include "chrome/browser/permissions/permission_request_manager.h"
#include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser.h"
...@@ -11,9 +12,11 @@ ...@@ -11,9 +12,11 @@
#include "chrome/browser/ui/views/payments/payment_request_dialog_view_ids.h" #include "chrome/browser/ui/views/payments/payment_request_dialog_view_ids.h"
#include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h" #include "chrome/test/base/ui_test_utils.h"
#include "components/autofill/core/browser/autofill_test_utils.h"
#include "components/content_settings/core/browser/host_content_settings_map.h" #include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/network_session_configurator/common/network_switches.h" #include "components/network_session_configurator/common/network_switches.h"
#include "components/payments/content/service_worker_payment_app_factory.h" #include "components/payments/content/service_worker_payment_app_factory.h"
#include "components/payments/core/features.h"
#include "components/payments/core/test_payment_manifest_downloader.h" #include "components/payments/core/test_payment_manifest_downloader.h"
#include "content/public/browser/browser_context.h" #include "content/public/browser/browser_context.h"
#include "content/public/browser/storage_partition.h" #include "content/public/browser/storage_partition.h"
...@@ -33,7 +36,7 @@ class PaymentRequestPaymentAppTest : public PaymentRequestBrowserTestBase { ...@@ -33,7 +36,7 @@ class PaymentRequestPaymentAppTest : public PaymentRequestBrowserTestBase {
bobpay_(net::EmbeddedTestServer::TYPE_HTTPS), bobpay_(net::EmbeddedTestServer::TYPE_HTTPS),
frankpay_(net::EmbeddedTestServer::TYPE_HTTPS) { frankpay_(net::EmbeddedTestServer::TYPE_HTTPS) {
scoped_feature_list_.InitAndEnableFeature( scoped_feature_list_.InitAndEnableFeature(
features::kServiceWorkerPaymentApps); ::features::kServiceWorkerPaymentApps);
} }
PermissionRequestManager* GetPermissionRequestManager() { PermissionRequestManager* GetPermissionRequestManager() {
...@@ -73,6 +76,26 @@ class PaymentRequestPaymentAppTest : public PaymentRequestBrowserTestBase { ...@@ -73,6 +76,26 @@ class PaymentRequestPaymentAppTest : public PaymentRequestBrowserTestBase {
<< contents; << contents;
} }
// Invokes the JavaScript function install(|method_name|) in
// components/test/data/payments/bobpay.com/app1/index.js, which responds
// back via domAutomationController.
void InstallBobPayForMethod(const std::string& method_name) {
ui_test_utils::NavigateToURL(browser(),
bobpay_.GetURL("bobpay.com", "/app1/"));
std::string contents;
std::string script = "install('" + method_name + "');";
ASSERT_TRUE(content::ExecuteScriptAndExtractString(
browser()->tab_strip_model()->GetActiveWebContents(), script,
&contents))
<< "Script execution failed: " << script;
ASSERT_NE(std::string::npos,
contents.find("Payment app for \"" + method_name +
"\" method installed."))
<< method_name << " method install message not found in:\n"
<< contents;
}
void BlockAlicePay() { void BlockAlicePay() {
GURL origin = alicepay_.GetURL("alicepay.com", "/app1/").GetOrigin(); GURL origin = alicepay_.GetURL("alicepay.com", "/app1/").GetOrigin();
HostContentSettingsMapFactory::GetForProfile(browser()->profile()) HostContentSettingsMapFactory::GetForProfile(browser()->profile())
...@@ -366,4 +389,99 @@ IN_PROC_BROWSER_TEST_F(PaymentRequestPaymentAppTest, PayWithBasicCard) { ...@@ -366,4 +389,99 @@ IN_PROC_BROWSER_TEST_F(PaymentRequestPaymentAppTest, PayWithBasicCard) {
} }
} }
IN_PROC_BROWSER_TEST_F(PaymentRequestPaymentAppTest, SkipUIEnabledWithBobPay) {
base::test::ScopedFeatureList features;
features.InitWithFeatures(
{
payments::features::kWebPaymentsSingleAppUiSkip,
::features::kServiceWorkerPaymentApps,
},
{});
InstallBobPayForMethod("https://bobpay.com");
{
SetDownloaderAndIgnorePortInAppScopeForTesting();
NavigateTo("/payment_request_bobpay_ui_skip_test.html");
// Since the skip UI flow is available, the request will complete without
// interaction besides hitting "pay" on the website.
ResetEventWaiterForSequence(
{DialogEvent::DIALOG_OPENED, DialogEvent::DIALOG_CLOSED});
content::WebContents* web_contents = GetActiveWebContents();
const std::string click_buy_button_js =
"(function() { document.getElementById('buy').click(); })();";
ASSERT_TRUE(content::ExecuteScript(web_contents, click_buy_button_js));
WaitForObservedEvent();
ExpectBodyContains({"bobpay.com"});
}
}
IN_PROC_BROWSER_TEST_F(PaymentRequestPaymentAppTest,
SkipUIDisabledWithMultipleAcceptedMethods) {
base::test::ScopedFeatureList features;
features.InitWithFeatures(
{
payments::features::kWebPaymentsSingleAppUiSkip,
::features::kServiceWorkerPaymentApps,
},
{});
InstallBobPayForMethod("https://bobpay.com");
{
SetDownloaderAndIgnorePortInAppScopeForTesting();
NavigateTo("/payment_request_bobpay_test.html");
// Since the skip UI flow is not available, the request will complete only
// after clicking on the Pay button in the dialog.
InvokePaymentRequestUI();
ResetEventWaiterForSequence(
{DialogEvent::PROCESSING_SPINNER_SHOWN, DialogEvent::DIALOG_CLOSED});
ClickOnDialogViewAndWait(DialogViewID::PAY_BUTTON, dialog_view());
ExpectBodyContains({"bobpay.com"});
}
}
IN_PROC_BROWSER_TEST_F(PaymentRequestPaymentAppTest,
SkipUIDisabledWithRequestedPayerEmail) {
base::test::ScopedFeatureList features;
features.InitWithFeatures(
{
payments::features::kWebPaymentsSingleAppUiSkip,
::features::kServiceWorkerPaymentApps,
},
{});
InstallBobPayForMethod("https://bobpay.com");
autofill::AutofillProfile profile(autofill::test::GetFullProfile());
AddAutofillProfile(profile);
{
SetDownloaderAndIgnorePortInAppScopeForTesting();
NavigateTo("/payment_request_bobpay_ui_skip_test.html");
// Since the skip UI flow is not available because the payer's email is
// requested, the request will complete only after clicking on the Pay
// button in the dialog.
ResetEventWaiter(DialogEvent::DIALOG_OPENED);
content::WebContents* web_contents = GetActiveWebContents();
const std::string click_buy_button_js =
"(function() { "
"document.getElementById('buyWithRequestedEmail').click(); })();";
ASSERT_TRUE(content::ExecuteScript(web_contents, click_buy_button_js));
WaitForObservedEvent();
EXPECT_TRUE(IsPayButtonEnabled());
ResetEventWaiterForSequence(
{DialogEvent::PROCESSING_SPINNER_SHOWN, DialogEvent::DIALOG_CLOSED});
ClickOnDialogViewAndWait(DialogViewID::PAY_BUTTON, dialog_view());
ExpectBodyContains({"bobpay.com"});
}
}
} // namespace payments } // namespace payments
...@@ -22,10 +22,9 @@ TestChromePaymentRequestDelegate::TestChromePaymentRequestDelegate( ...@@ -22,10 +22,9 @@ TestChromePaymentRequestDelegate::TestChromePaymentRequestDelegate(
is_browser_window_active_(is_browser_window_active) {} is_browser_window_active_(is_browser_window_active) {}
void TestChromePaymentRequestDelegate::ShowDialog(PaymentRequest* request) { void TestChromePaymentRequestDelegate::ShowDialog(PaymentRequest* request) {
PaymentRequestDialogView* dialog_view = hidden_dialog_ =
new PaymentRequestDialogView(request, observer_); std::make_unique<PaymentRequestDialogView>(request, observer_);
dialog_view->ShowDialog(); MaybeShowHiddenDialog(request);
dialog_ = dialog_view;
} }
bool TestChromePaymentRequestDelegate::IsIncognito() const { bool TestChromePaymentRequestDelegate::IsIncognito() const {
......
...@@ -41,7 +41,7 @@ class TestChromePaymentRequestDelegate : public ChromePaymentRequestDelegate { ...@@ -41,7 +41,7 @@ class TestChromePaymentRequestDelegate : public ChromePaymentRequestDelegate {
bool IsBrowserWindowActive() const override; bool IsBrowserWindowActive() const override;
PaymentRequestDialogView* dialog_view() { PaymentRequestDialogView* dialog_view() {
return static_cast<PaymentRequestDialogView*>(dialog_); return static_cast<PaymentRequestDialogView*>(shown_dialog_);
} }
private: private:
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include "components/payments/content/payment_request_converter.h" #include "components/payments/content/payment_request_converter.h"
#include "components/payments/content/payment_request_web_contents_manager.h" #include "components/payments/content/payment_request_web_contents_manager.h"
#include "components/payments/core/can_make_payment_query.h" #include "components/payments/core/can_make_payment_query.h"
#include "components/payments/core/features.h"
#include "components/payments/core/payment_details.h" #include "components/payments/core/payment_details.h"
#include "components/payments/core/payment_details_validation.h" #include "components/payments/core/payment_details_validation.h"
#include "components/payments/core/payment_prefs.h" #include "components/payments/core/payment_prefs.h"
...@@ -352,6 +353,19 @@ bool PaymentRequest::IsIncognito() const { ...@@ -352,6 +353,19 @@ bool PaymentRequest::IsIncognito() const {
return delegate_->IsIncognito(); return delegate_->IsIncognito();
} }
bool PaymentRequest::SatisfiesSkipUIConstraints() const {
return base::FeatureList::IsEnabled(features::kWebPaymentsSingleAppUiSkip) &&
base::FeatureList::IsEnabled(::features::kServiceWorkerPaymentApps) &&
state()->is_get_all_instruments_finished() &&
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()
// Only allowing URL base payment apps to skip the payment sheet.
&& spec()->url_payment_method_identifiers().size() == 1;
}
void PaymentRequest::RecordFirstAbortReason( void PaymentRequest::RecordFirstAbortReason(
JourneyLogger::AbortReason abort_reason) { JourneyLogger::AbortReason abort_reason) {
if (!has_recorded_completion_) { if (!has_recorded_completion_) {
...@@ -382,7 +396,7 @@ void PaymentRequest::RespondToCanMakePaymentQuery(bool can_make_payment, ...@@ -382,7 +396,7 @@ void PaymentRequest::RespondToCanMakePaymentQuery(bool can_make_payment,
if (delegate_->IsIncognito()) { if (delegate_->IsIncognito()) {
can_make_payment = can_make_payment =
spec()->HasBasicCardMethodName() || spec()->HasBasicCardMethodName() ||
base::FeatureList::IsEnabled(features::kServiceWorkerPaymentApps); base::FeatureList::IsEnabled(::features::kServiceWorkerPaymentApps);
} }
mojom::CanMakePaymentQueryResult positive = mojom::CanMakePaymentQueryResult positive =
......
...@@ -103,11 +103,19 @@ class PaymentRequest : public mojom::PaymentRequest, ...@@ -103,11 +103,19 @@ class PaymentRequest : public mojom::PaymentRequest,
bool IsIncognito() const; bool IsIncognito() const;
// Returns true if this payment request supports skipping the Payment Sheet.
// Typically, this means only one payment method is supported, it's a URL
// based method, and no other info is requested from the user.
bool SatisfiesSkipUIConstraints() const;
content::WebContents* web_contents() { return web_contents_; } content::WebContents* web_contents() { return web_contents_; }
PaymentRequestSpec* spec() { return spec_.get(); } PaymentRequestSpec* spec() { return spec_.get(); }
PaymentRequestState* state() { return state_.get(); } PaymentRequestState* state() { return state_.get(); }
PaymentRequestSpec* spec() const { return spec_.get(); }
PaymentRequestState* state() const { return state_.get(); }
private: private:
// Only records the abort reason if it's the first completion for this Payment // Only records the abort reason if it's the first completion for this Payment
// Request. This is necessary since the aborts cascade into one another with // Request. This is necessary since the aborts cascade into one another with
......
...@@ -22,6 +22,10 @@ class PaymentRequestDialog { ...@@ -22,6 +22,10 @@ class PaymentRequestDialog {
virtual void ShowDialog() = 0; virtual void ShowDialog() = 0;
virtual void ShowDialogAtPaymentHandlerSheet(
const GURL& url,
PaymentHandlerOpenWindowCallback callback) = 0;
virtual void CloseDialog() = 0; virtual void CloseDialog() = 0;
virtual void ShowErrorMessage() = 0; virtual void ShowErrorMessage() = 0;
......
...@@ -27,7 +27,6 @@ void PaymentRequestDisplayManager::DisplayHandle::Show( ...@@ -27,7 +27,6 @@ void PaymentRequestDisplayManager::DisplayHandle::Show(
PaymentRequest* request) { PaymentRequest* request) {
DCHECK(request); DCHECK(request);
DCHECK(delegate_); DCHECK(delegate_);
delegate_->ShowDialog(request); delegate_->ShowDialog(request);
} }
......
...@@ -21,5 +21,13 @@ const base::Feature kWebPaymentsMethodSectionOrderV2{ ...@@ -21,5 +21,13 @@ const base::Feature kWebPaymentsMethodSectionOrderV2{
const base::Feature kWebPaymentsModifiers{"WebPaymentsModifiers", const base::Feature kWebPaymentsModifiers{"WebPaymentsModifiers",
base::FEATURE_ENABLED_BY_DEFAULT}; base::FEATURE_ENABLED_BY_DEFAULT};
#if defined(OS_ANDROID)
const base::Feature kWebPaymentsSingleAppUiSkip{
"WebPaymentsSingleAppUiSkip", base::FEATURE_ENABLED_BY_DEFAULT};
#else
const base::Feature kWebPaymentsSingleAppUiSkip{
"WebPaymentsSingleAppUiSkip", base::FEATURE_DISABLED_BY_DEFAULT};
#endif
} // namespace features } // namespace features
} // namespace payments } // namespace payments
...@@ -27,6 +27,10 @@ extern const base::Feature kWebPaymentsMethodSectionOrderV2; ...@@ -27,6 +27,10 @@ extern const base::Feature kWebPaymentsMethodSectionOrderV2;
// Used to control the support for Payment Details modifiers. // Used to control the support for Payment Details modifiers.
extern const base::Feature kWebPaymentsModifiers; extern const base::Feature kWebPaymentsModifiers;
// Used to control whether the Payment Sheet can be skipped for Payment Requests
// with a single URL based payment app and no other info requested.
extern const base::Feature kWebPaymentsSingleAppUiSkip;
} // namespace features } // namespace features
} // namespace payments } // namespace payments
......
/*
* Copyright 2018 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.
*/
self.addEventListener('canmakepayment', (evt) => {
evt.respondWith(true);
});
self.addEventListener('paymentrequest', (evt) => {
evt.respondWith({
methodName: evt.methodData[0].supportedMethods,
details: {transactionId: '123'},
});
});
<!doctype html>
<!--
Copyright 2018 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.
-->
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bob Pay 1</title>
</head>
<body>
<script src="index.js"></script>
</body>
</html>
/*
* Copyright 2018 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.
*/
/**
* Prints output.
* @param {String} src - Where the message is coming from.
* @param {String} txt - The text to print.
*/
function output(src, txt) {
// Handle DOMException:
if (txt.message) {
txt = txt.message;
}
txt = src + ': ' + txt;
if (!domAutomationController) {
txt += ' window.domAutomationController not found.';
} else {
domAutomationController.send(txt);
}
console.log(txt);
}
/**
* Installs a payment app.
* @param {String} method - The payment method name that this app supports.
*/
function install(method) { // eslint-disable-line no-unused-vars
if (!navigator.serviceWorker) {
output('install()', 'ServiceWorker API not found.');
return;
}
navigator.serviceWorker.getRegistration('app.js')
.then((registration) => {
if (registration) {
output(
'serviceWorker.getRegistration()',
'The ServiceWorker is already installed.');
return;
}
navigator.serviceWorker.register('app.js')
.then(() => {
return navigator.serviceWorker.ready;
})
.then((registration) => {
if (!registration.paymentManager) {
output(
'serviceWorker.register()',
'PaymentManager API not found.');
return;
}
registration.paymentManager.instruments
.set('123456', {name: 'Bob Pay', enabledMethods: [method]})
.then(() => {
output(
'instruments.set()',
'Payment app for "' + method + '" method installed.');
})
.catch((error) => {
output('instruments.set()', error);
});
})
.catch((error) => {
output('serviceWorker.register()', error);
});
})
.catch((error) => {
output('serviceWorker.getRegistration()', error);
});
}
...@@ -20,6 +20,36 @@ function buy() { // eslint-disable-line no-unused-vars ...@@ -20,6 +20,36 @@ function buy() { // eslint-disable-line no-unused-vars
[{supportedMethods: ['https://bobpay.com']}], [{supportedMethods: ['https://bobpay.com']}],
{total: {label: 'Total', amount: {currency: 'USD', value: '5.00'}}}) {total: {label: 'Total', amount: {currency: 'USD', value: '5.00'}}})
.show() .show()
.then(function(resp) {
resp.complete('success')
.then(function() {
print(
resp.methodName + '<br>' +
JSON.stringify(resp.details, undefined, 2));
})
.catch(function(error) {
print('complete() rejected<br>' + error);
});
})
.catch(function(error) {
print('show() rejected<br>' + error);
});
} catch (error) {
print('exception thrown<br>' + error);
}
}
/**
* Launches the PaymentRequest UI with Bob Pay as the only payment method but
* requesting the payer's email as to disable skip ui.
*/
function buyWithRequestedEmail() { // eslint-disable-line no-unused-vars
try {
new PaymentRequest(
[{supportedMethods: ['https://bobpay.com']}],
{total: {label: 'Total', amount: {currency: 'USD', value: '5.00'}}},
{requestPayerEmail: true})
.show()
.then(function(resp) { .then(function(resp) {
resp.complete('success') resp.complete('success')
.then(function() { .then(function() {
......
...@@ -13,6 +13,7 @@ found in the LICENSE file. ...@@ -13,6 +13,7 @@ found in the LICENSE file.
</head> </head>
<body> <body>
<div><button onclick="buy()" id="buy">Bob Pay Test</button></div> <div><button onclick="buy()" id="buy">Bob Pay Test</button></div>
<div><button onclick="buyWithRequestedEmail()" id="buyWithRequestedEmail">Bob Pay Test</button></div>
<pre id="result"></pre> <pre id="result"></pre>
<script src="util.js"></script> <script src="util.js"></script>
<script src="bobpay_ui_skip.js"></script> <script src="bobpay_ui_skip.js"></script>
......
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