summaryrefslogtreecommitdiff
path: root/chromium/components/payments
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2020-10-29 10:46:47 +0100
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2020-11-02 12:02:10 +0000
commit99677208ff3b216fdfec551fbe548da5520cd6fb (patch)
tree476a4865c10320249360e859d8fdd3e01833b03a /chromium/components/payments
parentc30a6232df03e1efbd9f3b226777b07e087a1122 (diff)
downloadqtwebengine-chromium-99677208ff3b216fdfec551fbe548da5520cd6fb.tar.gz
BASELINE: Update Chromium to 86.0.4240.124
Change-Id: Ide0ff151e94cd665ae6521a446995d34a9d1d644 Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/components/payments')
-rw-r--r--chromium/components/payments/content/BUILD.gn75
-rw-r--r--chromium/components/payments/content/DEPS4
-rw-r--r--chromium/components/payments/content/android/BUILD.gn26
-rw-r--r--chromium/components/payments/content/android/can_make_payment_query_android.cc56
-rw-r--r--chromium/components/payments/content/android/java/res/layout/payment_handler_content.xml10
-rw-r--r--chromium/components/payments/content/android/java/res/layout/payment_minimal_ui_content.xml133
-rw-r--r--chromium/components/payments/content/android/java/res/layout/payment_minimal_ui_toolbar.xml119
-rw-r--r--chromium/components/payments/content/android/java/res/layout/payment_option_edit_icon.xml28
-rw-r--r--chromium/components/payments/content/android/java/res/layout/payment_request.xml48
-rw-r--r--chromium/components/payments/content/android/java/res/layout/payment_request_bottom_bar.xml52
-rw-r--r--chromium/components/payments/content/android/java/res/layout/payment_request_dropdown_item.xml20
-rw-r--r--chromium/components/payments/content/android/java/res/layout/payment_request_editor.xml43
-rw-r--r--chromium/components/payments/content/android/java/res/layout/payment_request_editor_dropdown.xml40
-rw-r--r--chromium/components/payments/content/android/java/res/layout/payment_request_editor_label.xml50
-rw-r--r--chromium/components/payments/content/android/java/res/layout/payment_request_header.xml67
-rw-r--r--chromium/components/payments/content/android/java/res/layout/payment_request_spinny.xml45
-rw-r--r--chromium/components/payments/content/android/java/res/layout/payments_request_editor_textview.xml57
-rw-r--r--chromium/components/payments/content/android/java/res/values-sw600dp/dimens.xml9
-rw-r--r--chromium/components/payments/content/android/java/res/values/dimens.xml41
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/BrowserPaymentRequest.java98
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/CanMakePaymentQuery.java53
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/ComponentPaymentRequestImpl.java567
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/JourneyLogger.java19
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/MojoPaymentRequestGateKeeper.java147
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/OWNERS1
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentApp.java8
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentAppFactoryParams.java87
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentFeatureList.java2
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentManifestWebDataService.java151
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentOptionsUtils.java90
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestLifecycleObserver.java23
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestParams.java39
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/SslValidityChecker.java47
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/SupportedDelegations.java99
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/OWNERS8
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelper.java22
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelperTypeConverter.java195
-rw-r--r--chromium/components/payments/content/android/payment_feature_list.cc2
-rw-r--r--chromium/components/payments/content/android/payments_java_resources.gni21
-rw-r--r--chromium/components/payments/content/android_app_communication.cc48
-rw-r--r--chromium/components/payments/content/android_app_communication.h114
-rw-r--r--chromium/components/payments/content/android_app_communication_chrome_os.cc345
-rw-r--r--chromium/components/payments/content/android_app_communication_stub.cc77
-rw-r--r--chromium/components/payments/content/android_app_communication_test_support.h102
-rw-r--r--chromium/components/payments/content/android_app_communication_test_support_chrome_os.cc160
-rw-r--r--chromium/components/payments/content/android_app_communication_test_support_stub.cc65
-rw-r--r--chromium/components/payments/content/android_app_communication_unittest.cc639
-rw-r--r--chromium/components/payments/content/android_payment_app.cc178
-rw-r--r--chromium/components/payments/content/android_payment_app.h97
-rw-r--r--chromium/components/payments/content/android_payment_app_factory.cc231
-rw-r--r--chromium/components/payments/content/android_payment_app_factory.h37
-rw-r--r--chromium/components/payments/content/android_payment_app_factory_unittest.cc519
-rw-r--r--chromium/components/payments/content/android_payment_app_unittest.cc164
-rw-r--r--chromium/components/payments/content/autofill_payment_app_factory.cc8
-rw-r--r--chromium/components/payments/content/autofill_payment_app_unittest.cc1
-rw-r--r--chromium/components/payments/content/content_payment_request_delegate.cc18
-rw-r--r--chromium/components/payments/content/content_payment_request_delegate.h25
-rw-r--r--chromium/components/payments/content/icon/BUILD.gn4
-rw-r--r--chromium/components/payments/content/manifest_verifier.cc9
-rw-r--r--chromium/components/payments/content/manifest_verifier.h7
-rw-r--r--chromium/components/payments/content/payment_app.cc4
-rw-r--r--chromium/components/payments/content/payment_app.h4
-rw-r--r--chromium/components/payments/content/payment_app_factory.h18
-rw-r--r--chromium/components/payments/content/payment_app_service.cc28
-rw-r--r--chromium/components/payments/content/payment_app_service.h7
-rw-r--r--chromium/components/payments/content/payment_app_service_factory.cc2
-rw-r--r--chromium/components/payments/content/payment_credential.cc129
-rw-r--r--chromium/components/payments/content/payment_credential.h84
-rw-r--r--chromium/components/payments/content/payment_handler_host.cc24
-rw-r--r--chromium/components/payments/content/payment_handler_host.h7
-rw-r--r--chromium/components/payments/content/payment_manifest_web_data_service.cc48
-rw-r--r--chromium/components/payments/content/payment_manifest_web_data_service.h35
-rw-r--r--chromium/components/payments/content/payment_method_manifest_table.cc134
-rw-r--r--chromium/components/payments/content/payment_method_manifest_table.h61
-rw-r--r--chromium/components/payments/content/payment_method_manifest_table_unittest.cc235
-rw-r--r--chromium/components/payments/content/payment_request.cc63
-rw-r--r--chromium/components/payments/content/payment_request.h21
-rw-r--r--chromium/components/payments/content/payment_request_dialog.h3
-rw-r--r--chromium/components/payments/content/payment_request_spec.cc12
-rw-r--r--chromium/components/payments/content/payment_request_spec.h9
-rw-r--r--chromium/components/payments/content/payment_request_state.cc93
-rw-r--r--chromium/components/payments/content/payment_request_state.h23
-rw-r--r--chromium/components/payments/content/payment_request_state_unittest.cc2
-rw-r--r--chromium/components/payments/content/payment_request_web_contents_manager.cc30
-rw-r--r--chromium/components/payments/content/payment_request_web_contents_manager.h21
-rw-r--r--chromium/components/payments/content/payment_response_helper.cc9
-rw-r--r--chromium/components/payments/content/payment_response_helper.h3
-rw-r--r--chromium/components/payments/content/secure_payment_confirmation_app.cc318
-rw-r--r--chromium/components/payments/content/secure_payment_confirmation_app.h115
-rw-r--r--chromium/components/payments/content/secure_payment_confirmation_app_factory.cc239
-rw-r--r--chromium/components/payments/content/secure_payment_confirmation_app_factory.h59
-rw-r--r--chromium/components/payments/content/secure_payment_confirmation_app_unittest.cc129
-rw-r--r--chromium/components/payments/content/secure_payment_confirmation_controller.cc207
-rw-r--r--chromium/components/payments/content/secure_payment_confirmation_controller.h80
-rw-r--r--chromium/components/payments/content/secure_payment_confirmation_model.cc18
-rw-r--r--chromium/components/payments/content/secure_payment_confirmation_model.h150
-rw-r--r--chromium/components/payments/content/secure_payment_confirmation_model_unittest.cc82
-rw-r--r--chromium/components/payments/content/secure_payment_confirmation_view.h45
-rw-r--r--chromium/components/payments/content/secure_payment_confirmation_view_stub.cc15
-rw-r--r--chromium/components/payments/content/service_worker_payment_app.cc30
-rw-r--r--chromium/components/payments/content/service_worker_payment_app.h11
-rw-r--r--chromium/components/payments/content/service_worker_payment_app_factory.cc69
-rw-r--r--chromium/components/payments/content/service_worker_payment_app_finder.cc25
-rw-r--r--chromium/components/payments/content/service_worker_payment_app_finder.h6
-rw-r--r--chromium/components/payments/content/service_worker_payment_app_finder_unittest.cc20
-rw-r--r--chromium/components/payments/content/test_content_payment_request_delegate.cc9
-rw-r--r--chromium/components/payments/content/test_content_payment_request_delegate.h3
-rw-r--r--chromium/components/payments/core/BUILD.gn26
-rw-r--r--chromium/components/payments/core/android_app_description.cc17
-rw-r--r--chromium/components/payments/core/android_app_description.h65
-rw-r--r--chromium/components/payments/core/android_app_description_tools.cc32
-rw-r--r--chromium/components/payments/core/android_app_description_tools.h29
-rw-r--r--chromium/components/payments/core/android_app_description_tools_unittest.cc85
-rw-r--r--chromium/components/payments/core/can_make_payment_query.cc69
-rw-r--r--chromium/components/payments/core/can_make_payment_query.h16
-rw-r--r--chromium/components/payments/core/can_make_payment_query_unittest.cc119
-rw-r--r--chromium/components/payments/core/chrome_os_error_strings.cc22
-rw-r--r--chromium/components/payments/core/chrome_os_error_strings.h30
-rw-r--r--chromium/components/payments/core/error_message_util.cc37
-rw-r--r--chromium/components/payments/core/error_message_util.h6
-rw-r--r--chromium/components/payments/core/error_strings.cc2
-rw-r--r--chromium/components/payments/core/error_strings.h7
-rw-r--r--chromium/components/payments/core/features.cc27
-rw-r--r--chromium/components/payments/core/features.h16
-rw-r--r--chromium/components/payments/core/journey_logger.cc33
-rw-r--r--chromium/components/payments/core/journey_logger.h53
-rw-r--r--chromium/components/payments/core/journey_logger_unittest.cc19
-rw-r--r--chromium/components/payments/core/method_strings.cc1
-rw-r--r--chromium/components/payments/core/method_strings.h4
-rw-r--r--chromium/components/payments/core/native_error_strings.cc17
-rw-r--r--chromium/components/payments/core/native_error_strings.h21
-rw-r--r--chromium/components/payments/core/payment_manifest_downloader_unittest.cc1
-rw-r--r--chromium/components/payments/core/payment_request_data_util.cc16
-rw-r--r--chromium/components/payments/core/payment_request_data_util.h19
-rw-r--r--chromium/components/payments/core/payment_request_data_util_unittest.cc23
-rw-r--r--chromium/components/payments/core/payment_request_delegate.h10
-rw-r--r--chromium/components/payments/core/secure_payment_confirmation_instrument.cc32
-rw-r--r--chromium/components/payments/core/secure_payment_confirmation_instrument.h51
-rw-r--r--chromium/components/payments/core/test_payment_request_delegate.h1
139 files changed, 8735 insertions, 360 deletions
diff --git a/chromium/components/payments/content/BUILD.gn b/chromium/components/payments/content/BUILD.gn
index ab9091d996b..16c9e56ab38 100644
--- a/chromium/components/payments/content/BUILD.gn
+++ b/chromium/components/payments/content/BUILD.gn
@@ -2,10 +2,14 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-import("//build/config/jumbo.gni")
-
-jumbo_static_library("content") {
+static_library("content") {
sources = [
+ "android_app_communication.cc",
+ "android_app_communication.h",
+ "android_payment_app.cc",
+ "android_payment_app.h",
+ "android_payment_app_factory.cc",
+ "android_payment_app_factory.h",
"autofill_payment_app.cc",
"autofill_payment_app.h",
"autofill_payment_app_factory.cc",
@@ -32,6 +36,11 @@ jumbo_static_library("content") {
"payment_request_converter.h",
"payment_request_spec.cc",
"payment_request_spec.h",
+ "secure_payment_confirmation_app.cc",
+ "secure_payment_confirmation_app.h",
+ "secure_payment_confirmation_app_factory.cc",
+ "secure_payment_confirmation_app_factory.h",
+ "secure_payment_confirmation_view.h",
"service_worker_payment_app.cc",
"service_worker_payment_app.h",
"service_worker_payment_app_factory.cc",
@@ -41,6 +50,7 @@ jumbo_static_library("content") {
deps = [
":content_common",
":utils",
+ "//base",
"//components/autofill/core/browser",
"//components/keyed_service/content",
"//components/payments/content/utility",
@@ -52,14 +62,36 @@ jumbo_static_library("content") {
"//components/strings:components_strings_grit",
"//components/ukm/content",
"//components/url_formatter",
+ "//components/webdata/common",
"//content/public/browser",
+ "//crypto",
+ "//device/fido",
+ "//services/data_decoder/public/cpp",
"//third_party/blink/public:blink_headers",
"//url",
]
- if (!is_android) {
+ if (is_chromeos) {
+ sources += [ "android_app_communication_chrome_os.cc" ]
+
+ deps += [
+ "//components/arc",
+ "//components/arc/mojom",
+ ]
+ }
+
+ if (!is_chromeos) {
+ sources += [ "android_app_communication_stub.cc" ]
+ }
+
+ if (is_android) {
+ sources += [ "secure_payment_confirmation_view_stub.cc" ]
+ } else {
sources += [
+ "content_payment_request_delegate.cc",
"content_payment_request_delegate.h",
+ "payment_credential.cc",
+ "payment_credential.h",
"payment_request.cc",
"payment_request.h",
"payment_request_dialog.h",
@@ -71,12 +103,16 @@ jumbo_static_library("content") {
"payment_request_web_contents_manager.h",
"payment_response_helper.cc",
"payment_response_helper.h",
+ "secure_payment_confirmation_controller.cc",
+ "secure_payment_confirmation_controller.h",
+ "secure_payment_confirmation_model.cc",
+ "secure_payment_confirmation_model.h",
]
}
}
# Files used by content and utility.
-jumbo_static_library("content_common") {
+static_library("content_common") {
sources = [
"web_app_manifest.cc",
"web_app_manifest.h",
@@ -88,7 +124,7 @@ jumbo_static_library("content_common") {
]
}
-jumbo_static_library("utils") {
+static_library("utils") {
sources = [
"developer_console_logger.cc",
"developer_console_logger.h",
@@ -129,13 +165,15 @@ jumbo_static_library("utils") {
]
}
-jumbo_source_set("unit_tests") {
+source_set("unit_tests") {
testonly = true
sources = [
+ "android_app_communication_test_support.h",
+ "android_app_communication_unittest.cc",
+ "android_payment_app_factory_unittest.cc",
+ "android_payment_app_unittest.cc",
"payment_method_manifest_table_unittest.cc",
"service_worker_payment_app_finder_unittest.cc",
- "test_content_payment_request_delegate.cc",
- "test_content_payment_request_delegate.h",
"web_app_manifest_section_table_unittest.cc",
]
@@ -146,7 +184,11 @@ jumbo_source_set("unit_tests") {
"payment_request_spec_unittest.cc",
"payment_request_state_unittest.cc",
"payment_response_helper_unittest.cc",
+ "secure_payment_confirmation_app_unittest.cc",
+ "secure_payment_confirmation_model_unittest.cc",
"service_worker_payment_app_unittest.cc",
+ "test_content_payment_request_delegate.cc",
+ "test_content_payment_request_delegate.h",
]
}
@@ -159,6 +201,7 @@ jumbo_source_set("unit_tests") {
"//components/autofill/core/browser:test_support",
"//components/payments/core",
"//components/payments/core:error_strings",
+ "//components/payments/core:method_strings",
"//components/payments/core:test_support",
"//components/strings:components_strings_grit",
"//components/webdata/common",
@@ -172,4 +215,18 @@ jumbo_source_set("unit_tests") {
"//third_party/icu",
"//third_party/libaddressinput:test_support",
]
+
+ if (is_chromeos) {
+ sources += [ "android_app_communication_test_support_chrome_os.cc" ]
+
+ deps += [
+ "//components/arc",
+ "//components/arc:arc_test_support",
+ "//components/arc/mojom",
+ ]
+ }
+
+ if (!is_chromeos) {
+ sources += [ "android_app_communication_test_support_stub.cc" ]
+ }
}
diff --git a/chromium/components/payments/content/DEPS b/chromium/components/payments/content/DEPS
index 117c4ac539b..35b832b03d5 100644
--- a/chromium/components/payments/content/DEPS
+++ b/chromium/components/payments/content/DEPS
@@ -1,5 +1,6 @@
include_rules = [
"-components/payments/content/android",
+ "+components/arc",
"+components/autofill",
"+components/keyed_service/content",
"+components/keyed_service/core",
@@ -9,6 +10,8 @@ include_rules = [
"+components/url_formatter",
"+components/webdata/common",
"+content/public",
+ "+crypto",
+ "+device/fido",
"+mojo/public/cpp",
"+net",
"+services/data_decoder/public/cpp",
@@ -20,6 +23,7 @@ include_rules = [
"+third_party/blink/public/common",
"+third_party/blink/public/mojom/devtools/console_message.mojom.h",
"+third_party/blink/public/mojom/payments",
+ "+third_party/blink/public/mojom/webauthn",
"+third_party/blink/public/platform/modules/payments",
"+third_party/skia/include/core/SkBitmap.h",
"+ui/base",
diff --git a/chromium/components/payments/content/android/BUILD.gn b/chromium/components/payments/content/android/BUILD.gn
index ba4855549cb..e73979c2543 100644
--- a/chromium/components/payments/content/android/BUILD.gn
+++ b/chromium/components/payments/content/android/BUILD.gn
@@ -4,12 +4,14 @@
import("//build/config/android/config.gni")
import("//build/config/android/rules.gni")
+import("//components/payments/content/android/payments_java_resources.gni")
import("//mojo/public/tools/bindings/mojom.gni")
static_library("android") {
sources = [
"byte_buffer_helper.cc",
"byte_buffer_helper.h",
+ "can_make_payment_query_android.cc",
"currency_formatter_android.cc",
"currency_formatter_android.h",
"error_message_util.cc",
@@ -48,6 +50,7 @@ static_library("android") {
generate_jni("jni_headers") {
sources = [
+ "java/src/org/chromium/components/payments/CanMakePaymentQuery.java",
"java/src/org/chromium/components/payments/CurrencyFormatter.java",
"java/src/org/chromium/components/payments/ErrorMessageUtil.java",
"java/src/org/chromium/components/payments/JniPaymentApp.java",
@@ -57,27 +60,39 @@ generate_jni("jni_headers") {
"java/src/org/chromium/components/payments/PaymentHandlerHost.java",
"java/src/org/chromium/components/payments/PaymentManifestDownloader.java",
"java/src/org/chromium/components/payments/PaymentManifestParser.java",
+ "java/src/org/chromium/components/payments/PaymentManifestWebDataService.java",
"java/src/org/chromium/components/payments/PaymentRequestSpec.java",
"java/src/org/chromium/components/payments/PaymentRequestUpdateEventListener.java",
"java/src/org/chromium/components/payments/PaymentValidator.java",
+ "java/src/org/chromium/components/payments/SslValidityChecker.java",
"java/src/org/chromium/components/payments/UrlUtil.java",
]
}
+android_resources("java_resources") {
+ create_srcjar = false
+ sources = payments_java_resources
+}
+
android_library("java") {
annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
sources = [
"java/src/org/chromium/components/payments/Address.java",
+ "java/src/org/chromium/components/payments/BrowserPaymentRequest.java",
+ "java/src/org/chromium/components/payments/CanMakePaymentQuery.java",
+ "java/src/org/chromium/components/payments/ComponentPaymentRequestImpl.java",
"java/src/org/chromium/components/payments/CurrencyFormatter.java",
"java/src/org/chromium/components/payments/ErrorMessageUtil.java",
"java/src/org/chromium/components/payments/JniPaymentApp.java",
"java/src/org/chromium/components/payments/JourneyLogger.java",
+ "java/src/org/chromium/components/payments/MojoPaymentRequestGateKeeper.java",
"java/src/org/chromium/components/payments/MojoStructCollection.java",
"java/src/org/chromium/components/payments/OriginSecurityChecker.java",
"java/src/org/chromium/components/payments/PackageManagerDelegate.java",
"java/src/org/chromium/components/payments/PayerData.java",
"java/src/org/chromium/components/payments/PaymentAddressTypeConverter.java",
"java/src/org/chromium/components/payments/PaymentApp.java",
+ "java/src/org/chromium/components/payments/PaymentAppFactoryParams.java",
"java/src/org/chromium/components/payments/PaymentDetailsConverter.java",
"java/src/org/chromium/components/payments/PaymentDetailsUpdateService.java",
"java/src/org/chromium/components/payments/PaymentDetailsUpdateServiceHelper.java",
@@ -85,22 +100,32 @@ android_library("java") {
"java/src/org/chromium/components/payments/PaymentHandlerHost.java",
"java/src/org/chromium/components/payments/PaymentManifestDownloader.java",
"java/src/org/chromium/components/payments/PaymentManifestParser.java",
+ "java/src/org/chromium/components/payments/PaymentManifestWebDataService.java",
+ "java/src/org/chromium/components/payments/PaymentOptionsUtils.java",
+ "java/src/org/chromium/components/payments/PaymentRequestLifecycleObserver.java",
+ "java/src/org/chromium/components/payments/PaymentRequestParams.java",
"java/src/org/chromium/components/payments/PaymentRequestSpec.java",
"java/src/org/chromium/components/payments/PaymentRequestUpdateEventListener.java",
"java/src/org/chromium/components/payments/PaymentValidator.java",
+ "java/src/org/chromium/components/payments/SslValidityChecker.java",
+ "java/src/org/chromium/components/payments/SupportedDelegations.java",
"java/src/org/chromium/components/payments/UrlUtil.java",
"java/src/org/chromium/components/payments/WebAppManifestSection.java",
"java/src/org/chromium/components/payments/intent/IsReadyToPayServiceHelper.java",
"java/src/org/chromium/components/payments/intent/WebPaymentIntentHelper.java",
"java/src/org/chromium/components/payments/intent/WebPaymentIntentHelperType.java",
+ "java/src/org/chromium/components/payments/intent/WebPaymentIntentHelperTypeConverter.java",
]
deps = [
+ ":java_resources",
"//base:base_java",
"//base:jni_java",
"//components/autofill/android:autofill_java",
"//components/payments/mojom:mojom_java",
"//content/public/android:content_java",
"//mojo/public/java:bindings_java",
+ "//mojo/public/java:system_java",
+ "//third_party/android_deps:androidx_annotation_annotation_java",
"//third_party/blink/public/mojom:android_mojo_bindings_java",
"//url:gurl_java",
"//url:origin_java",
@@ -111,6 +136,7 @@ android_library("java") {
":payment_details_update_service_aidl",
":payments_journey_logger_enum_javagen",
]
+ resources_package = "org.chromium.components.payments"
}
android_aidl("payment_details_update_service_aidl") {
diff --git a/chromium/components/payments/content/android/can_make_payment_query_android.cc b/chromium/components/payments/content/android/can_make_payment_query_android.cc
new file mode 100644
index 00000000000..cf0535a7a40
--- /dev/null
+++ b/chromium/components/payments/content/android/can_make_payment_query_android.cc
@@ -0,0 +1,56 @@
+// 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.
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/android/jni_array.h"
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "components/payments/content/android/jni_headers/CanMakePaymentQuery_jni.h"
+#include "components/payments/content/can_make_payment_query_factory.h"
+#include "components/payments/core/can_make_payment_query.h"
+#include "content/public/browser/web_contents.h"
+#include "url/gurl.h"
+
+namespace payments {
+
+// static
+jboolean JNI_CanMakePaymentQuery_CanQuery(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& jweb_contents,
+ const base::android::JavaParamRef<jstring>& jtop_level_origin,
+ const base::android::JavaParamRef<jstring>& jframe_origin,
+ const base::android::JavaParamRef<jobject>& jquery_map) {
+ auto* web_contents = content::WebContents::FromJavaWebContents(jweb_contents);
+ if (!web_contents)
+ return false;
+
+ std::vector<std::string> method_identifiers;
+ base::android::AppendJavaStringArrayToStringVector(
+ env, Java_CanMakePaymentQuery_getMethodIdentifiers(env, jquery_map),
+ &method_identifiers);
+
+ std::map<std::string, std::set<std::string>> query;
+ for (const auto& method_identifier : method_identifiers) {
+ std::set<std::string> method_specific_parameters = {
+ base::android::ConvertJavaStringToUTF8(
+ Java_CanMakePaymentQuery_getStringifiedMethodData(
+ env, jquery_map,
+ base::android::ConvertUTF8ToJavaString(env,
+ method_identifier)))};
+ query.insert(std::make_pair(method_identifier, method_specific_parameters));
+ }
+
+ return CanMakePaymentQueryFactory::GetInstance()
+ ->GetForContext(web_contents->GetBrowserContext())
+ ->CanQuery(
+ GURL(base::android::ConvertJavaStringToUTF8(env, jtop_level_origin)),
+ GURL(base::android::ConvertJavaStringToUTF8(env, jframe_origin)),
+ query);
+}
+
+} // namespace payments
diff --git a/chromium/components/payments/content/android/java/res/layout/payment_handler_content.xml b/chromium/components/payments/content/android/java/res/layout/payment_handler_content.xml
new file mode 100644
index 00000000000..b3af8a30a9d
--- /dev/null
+++ b/chromium/components/payments/content/android/java/res/layout/payment_handler_content.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2019 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. -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+</FrameLayout>
diff --git a/chromium/components/payments/content/android/java/res/layout/payment_minimal_ui_content.xml b/chromium/components/payments/content/android/java/res/layout/payment_minimal_ui_content.xml
new file mode 100644
index 00000000000..71707d60e2e
--- /dev/null
+++ b/chromium/components/payments/content/android/java/res/layout/payment_minimal_ui_content.xml
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2019 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. -->
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:background="@color/sheet_bg_color"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent">
+
+ <!-- "Payment" label. -->
+ <TextView
+ android:id="@+id/payment_label"
+ android:layout_alignParentStart="true"
+ android:layout_height="wrap_content"
+ android:layout_toStartOf="@+id/payment_currency"
+ android:layout_width="wrap_content"
+ android:paddingBottom="@dimen/payment_minimal_ui_large_text_vertical_spacing"
+ android:paddingEnd="@dimen/payment_minimal_ui_spacing"
+ android:paddingStart="@dimen/payment_minimal_ui_spacing"
+ android:paddingTop="@dimen/payment_minimal_ui_content_top_spacing"
+ android:textAppearance="@style/TextAppearance.TextMedium.Secondary"
+ android:text="@string/payment_request_payment_method_section_name"/>
+
+ <!-- Currency label, e.g., "USD". -->
+ <TextView
+ android:id="@id/payment_currency"
+ android:layout_height="wrap_content"
+ android:layout_toStartOf="@+id/payment_amount"
+ android:layout_width="wrap_content"
+ android:paddingTop="@dimen/payment_minimal_ui_content_top_spacing"
+ android:textAppearance="@style/TextAppearance.TextMedium.Primary"/>
+
+ <!-- Bold formatted amount, e.g., "$1.00". -->
+ <TextView
+ android:id="@id/payment_amount"
+ android:layout_alignParentEnd="true"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:paddingEnd="@dimen/payment_minimal_ui_spacing"
+ android:paddingStart="@dimen/payment_minimal_ui_amount_currency_spacing"
+ android:paddingTop="@dimen/payment_minimal_ui_content_top_spacing"
+ android:textAppearance="@style/TextAppearance.TextMediumThick.Primary"/>
+
+ <!-- Line item separator. -->
+ <View
+ android:background="@color/divider_line_bg_color"
+ android:id="@+id/line_item_separator"
+ android:layout_below="@id/payment_label"
+ android:layout_height="@dimen/payment_minimal_ui_separator_width"
+ android:layout_marginEnd="@dimen/payment_minimal_ui_spacing"
+ android:layout_marginStart="@dimen/payment_minimal_ui_spacing"
+ android:layout_width="match_parent"/>
+
+ <!-- "Account Balance" label. -->
+ <TextView
+ android:id="@+id/account_balance_label"
+ android:layout_alignParentStart="true"
+ android:layout_below="@id/line_item_separator"
+ android:layout_height="wrap_content"
+ android:layout_toStartOf="@+id/account_balance_currency"
+ android:layout_width="wrap_content"
+ android:paddingBottom="@dimen/payment_minimal_ui_large_text_vertical_spacing"
+ android:paddingEnd="@dimen/payment_minimal_ui_spacing"
+ android:paddingStart="@dimen/payment_minimal_ui_spacing"
+ android:paddingTop="@dimen/payment_minimal_ui_large_text_vertical_spacing"
+ android:textAppearance="@style/TextAppearance.TextMedium.Secondary"
+ android:text="@string/payment_account_balance"/>
+
+ <!-- Currency label, e.g., "USD". -->
+ <TextView
+ android:id="@id/account_balance_currency"
+ android:layout_below="@id/line_item_separator"
+ android:layout_height="wrap_content"
+ android:layout_toStartOf="@+id/account_balance"
+ android:layout_width="wrap_content"
+ android:paddingTop="@dimen/payment_minimal_ui_large_text_vertical_spacing"
+ android:textAppearance="@style/TextAppearance.TextMedium.Primary"/>
+
+ <!-- Bold formatted account balance, e.g., "$18.00". -->
+ <TextView
+ android:id="@id/account_balance"
+ android:layout_alignParentEnd="true"
+ android:layout_below="@id/line_item_separator"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:paddingEnd="@dimen/payment_minimal_ui_spacing"
+ android:paddingStart="@dimen/payment_minimal_ui_amount_currency_spacing"
+ android:paddingTop="@dimen/payment_minimal_ui_large_text_vertical_spacing"
+ android:textAppearance="@style/TextAppearance.TextMediumThick.Primary"/>
+
+ <!-- Status icon, e.g., the fingerprint icon. -->
+ <ImageView
+ android:id="@+id/status_icon"
+ android:importantForAccessibility="no"
+ android:layout_below="@id/account_balance_label"
+ android:layout_centerHorizontal="true"
+ android:layout_height="@dimen/payment_minimal_ui_content_icon_size"
+ android:layout_margin="@dimen/payment_minimal_ui_content_icon_spacing"
+ android:layout_width="@dimen/payment_minimal_ui_content_icon_size"
+ android:scaleType="centerCrop"/>
+
+ <!-- Processing spinner. -->
+ <ProgressBar
+ android:id="@+id/processing_spinner"
+ android:layout_below="@id/account_balance_label"
+ android:layout_centerHorizontal="true"
+ android:layout_height="@dimen/payment_minimal_ui_content_icon_size"
+ android:layout_margin="@dimen/payment_minimal_ui_content_icon_spacing"
+ android:layout_width="@dimen/payment_minimal_ui_content_icon_size"/>
+
+ <!-- Status message, e.g., "Touch sensor to pay". -->
+ <TextView
+ android:id="@+id/status_message"
+ android:layout_below="@id/status_icon"
+ android:layout_centerHorizontal="true"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:paddingBottom="@dimen/payment_minimal_ui_content_bottom_spacing"
+ android:textAppearance="@style/TextAppearance.TextSmall.Secondary"/>
+
+ <!-- "Pay" button. -->
+ <org.chromium.ui.widget.ButtonCompat
+ android:id="@+id/pay_button"
+ android:layout_alignParentEnd="true"
+ android:layout_below="@id/account_balance"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/payment_minimal_ui_spacing"
+ android:layout_width="wrap_content"
+ android:text="@string/payments_pay_button"
+ style="@style/FilledButton.Flat"/>
+
+</RelativeLayout>
diff --git a/chromium/components/payments/content/android/java/res/layout/payment_minimal_ui_toolbar.xml b/chromium/components/payments/content/android/java/res/layout/payment_minimal_ui_toolbar.xml
new file mode 100644
index 00000000000..e8daeb1449a
--- /dev/null
+++ b/chromium/components/payments/content/android/java/res/layout/payment_minimal_ui_toolbar.xml
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2019 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. -->
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:background="@color/sheet_bg_color"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent">
+
+ <!-- Drag handle. -->
+ <ImageView
+ android:id="@+id/drag_handle"
+ android:importantForAccessibility="no"
+ android:layout_centerHorizontal="true"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:padding="@dimen/payment_minimal_ui_drag_handle_spacing"
+ android:src="@drawable/drag_handlebar"/>
+
+ <!-- Payment app icon. -->
+ <ImageView
+ android:id="@+id/payment_app_icon"
+ android:importantForAccessibility="no"
+ android:layout_alignParentStart="true"
+ android:layout_below="@id/drag_handle"
+ android:layout_height="@dimen/payment_minimal_ui_toolbar_icon_size"
+ android:layout_marginBottom="@dimen/payment_minimal_ui_app_icon_bottom_spacing"
+ android:layout_marginEnd="@dimen/payment_minimal_ui_app_icon_horizontal_spacing"
+ android:layout_marginStart="@dimen/payment_minimal_ui_app_icon_horizontal_spacing"
+ android:layout_width="@dimen/payment_minimal_ui_toolbar_icon_size"
+ android:scaleType="centerCrop"/>
+
+ <!-- Large status message, e.g., "Not recognized" or "Complete". -->
+ <TextView
+ android:alpha="0"
+ android:id="@+id/large_status_message"
+ android:layout_below="@id/drag_handle"
+ android:layout_height="wrap_content"
+ android:layout_toEndOf="@id/payment_app_icon"
+ android:layout_width="wrap_content"
+ android:paddingTop="@dimen/payment_minimal_ui_large_text_vertical_spacing"
+ android:textAppearance="@style/TextAppearance.TextMedium.Primary"/>
+
+ <!-- Payment app name, e.g., "Bob Pay". -->
+ <TextView
+ android:alpha="0"
+ android:id="@+id/payment_app_name"
+ android:layout_below="@id/drag_handle"
+ android:layout_height="wrap_content"
+ android:layout_toEndOf="@id/payment_app_icon"
+ android:layout_width="wrap_content"
+ android:paddingTop="@dimen/payment_minimal_ui_large_text_vertical_spacing"
+ android:textAppearance="@style/TextAppearance.TextLarge.Primary"/>
+
+ <!-- Small emphasized status message, e.g., "Touch sensor to pay". -->
+ <TextView
+ android:id="@+id/small_emphasized_status_message"
+ android:layout_below="@id/drag_handle"
+ android:layout_height="wrap_content"
+ android:layout_toEndOf="@id/payment_app_icon"
+ android:layout_width="wrap_content"
+ android:paddingTop="@dimen/payment_minimal_ui_toolbar_small_status_text_top_spacing"
+ android:textAppearance="@style/TextAppearance.TextSmall.Blue"/>
+
+ <!-- Currency label, e.g., "USD". -->
+ <TextView
+ android:id="@+id/currency"
+ android:layout_below="@id/small_emphasized_status_message"
+ android:layout_height="wrap_content"
+ android:layout_toEndOf="@id/payment_app_icon"
+ android:layout_width="wrap_content"
+ android:textAppearance="@style/TextAppearance.TextMedium.Primary"/>
+
+ <!-- Bold formatted amount, e.g., "$1.00". -->
+ <TextView
+ android:id="@+id/amount"
+ android:layout_below="@id/small_emphasized_status_message"
+ android:layout_height="wrap_content"
+ android:layout_toEndOf="@id/currency"
+ android:layout_width="wrap_content"
+ android:paddingStart="@dimen/payment_minimal_ui_amount_currency_spacing"
+ android:textAppearance="@style/TextAppearance.TextMediumThick.Primary"/>
+
+ <!-- Status icon, e.g., the fingerprint icon. -->
+ <ImageView
+ android:id="@+id/status_icon"
+ android:importantForAccessibility="no"
+ android:layout_alignParentEnd="true"
+ android:layout_below="@id/drag_handle"
+ android:layout_height="@dimen/payment_minimal_ui_toolbar_icon_size"
+ android:layout_marginEnd="@dimen/payment_minimal_ui_spacing"
+ android:layout_marginStart="@dimen/payment_minimal_ui_spacing"
+ android:layout_width="@dimen/payment_minimal_ui_toolbar_icon_size"
+ android:scaleType="centerCrop"/>
+
+ <!-- Processing spinner. -->
+ <ProgressBar
+ android:id="@+id/processing_spinner"
+ android:layout_alignParentEnd="true"
+ android:layout_below="@id/drag_handle"
+ android:layout_height="@dimen/payment_minimal_ui_toolbar_icon_size"
+ android:layout_marginEnd="@dimen/payment_minimal_ui_spacing"
+ android:layout_marginStart="@dimen/payment_minimal_ui_spacing"
+ android:layout_width="@dimen/payment_minimal_ui_toolbar_icon_size"/>
+
+ <!-- "Pay" button. -->
+ <org.chromium.ui.widget.ButtonCompat
+ android:id="@+id/pay_button"
+ android:layout_alignParentEnd="true"
+ android:layout_below="@id/drag_handle"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/payment_minimal_ui_spacing"
+ android:layout_marginStart="@dimen/payment_minimal_ui_spacing"
+ android:layout_width="wrap_content"
+ android:text="@string/payments_pay_button"
+ style="@style/FilledButton.Flat"/>
+
+</RelativeLayout>
diff --git a/chromium/components/payments/content/android/java/res/layout/payment_option_edit_icon.xml b/chromium/components/payments/content/android/java/res/layout/payment_option_edit_icon.xml
new file mode 100644
index 00000000000..d26c8040fb1
--- /dev/null
+++ b/chromium/components/payments/content/android/java/res/layout/payment_option_edit_icon.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2016 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. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/payments_open_editor_pencil_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:contentDescription="@string/payments_edit_button">
+
+ <View
+ android:layout_width="1dp"
+ android:layout_height="48dp"
+ android:layout_gravity="center_vertical"
+ android:layout_marginStart="@dimen/editor_dialog_section_large_spacing"
+ android:layout_marginEnd="@dimen/editor_dialog_section_large_spacing"
+ style="@style/VerticalDivider" />
+
+ <org.chromium.ui.widget.ChromeImageView
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_gravity="center_vertical"
+ android:src="@drawable/ic_edit_24dp"
+ android:importantForAccessibility="no"
+ app:tint="@color/default_icon_color"/>
+</LinearLayout>
diff --git a/chromium/components/payments/content/android/java/res/layout/payment_request.xml b/chromium/components/payments/content/android/java/res/layout/payment_request.xml
new file mode 100644
index 00000000000..d6f6c44d6a5
--- /dev/null
+++ b/chromium/components/payments/content/android/java/res/layout/payment_request.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2016 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. -->
+
+<!-- PaymentRequestUI dialog
+ Sits at the bottom of the screen like a Bottom Sheet.
+-->
+<org.chromium.components.browser_ui.widget.BoundedLinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/payment_request"
+ android:orientation="vertical"
+ android:gravity="center"
+ app:maxWidthLandscape="@dimen/payments_ui_max_dialog_width"
+ app:maxWidthPortrait="@dimen/payments_ui_max_dialog_width"
+ android:background="@color/payment_request_bg" >
+
+ <include layout="@layout/payment_request_header" />
+ <include layout="@layout/payment_request_spinny" />
+
+ <org.chromium.components.browser_ui.widget.FadingEdgeScrollView
+ android:id="@+id/option_container"
+ android:layout_height="0dp"
+ android:layout_width="match_parent"
+ android:layout_weight="1"
+ android:visibility="gone" >
+
+ <LinearLayout
+ android:id="@+id/payment_container_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/retry_error"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="16dp"
+ android:visibility="gone"
+ android:textAppearance="@style/TextAppearance.PaymentsUiSectionWarningText" />
+ </LinearLayout>
+
+ </org.chromium.components.browser_ui.widget.FadingEdgeScrollView>
+
+ <include layout="@layout/payment_request_bottom_bar" />
+
+</org.chromium.components.browser_ui.widget.BoundedLinearLayout>
diff --git a/chromium/components/payments/content/android/java/res/layout/payment_request_bottom_bar.xml b/chromium/components/payments/content/android/java/res/layout/payment_request_bottom_bar.xml
new file mode 100644
index 00000000000..f91344db848
--- /dev/null
+++ b/chromium/components/payments/content/android/java/res/layout/payment_request_bottom_bar.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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. -->
+
+<!-- Sits at the bottom of the payment request UI. -->
+<org.chromium.chrome.browser.payments.ui.PaymentRequestBottomBar
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/bottom_bar"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:paddingTop="@dimen/payments_request_bottom_bar_vertical_padding"
+ android:paddingBottom="@dimen/payments_request_bottom_bar_vertical_padding"
+ android:paddingStart="@dimen/payments_request_bottom_bar_horizontal_padding"
+ android:paddingEnd="@dimen/payments_request_bottom_bar_horizontal_padding"
+ android:background="@color/payment_request_bg"
+ android:visibility="gone" >
+
+ <ImageView
+ android:id="@+id/logo_name"
+ android:layout_width="72dp"
+ android:layout_height="20dp"
+ android:src="@drawable/product_logo_name"
+ tools:ignore="ContentDescription" />
+
+ <ImageView
+ android:id="@+id/logo"
+ android:layout_width="20dp"
+ android:layout_height="20dp"
+ android:src="@drawable/fre_product_logo"
+ tools:ignore="ContentDescription" />
+
+ <Space
+ android:id="@+id/space"
+ android:layout_width="0dp"
+ android:layout_height="0dp" />
+
+ <org.chromium.ui.widget.ButtonCompat
+ android:id="@+id/button_secondary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/payments_edit_button"
+ style="@style/TextButton" />
+
+ <org.chromium.ui.widget.ButtonCompat
+ android:id="@+id/button_primary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/payments_pay_button"
+ style="@style/FilledButton.Flat" />
+</org.chromium.chrome.browser.payments.ui.PaymentRequestBottomBar> \ No newline at end of file
diff --git a/chromium/components/payments/content/android/java/res/layout/payment_request_dropdown_item.xml b/chromium/components/payments/content/android/java/res/layout/payment_request_dropdown_item.xml
new file mode 100644
index 00000000000..67be52073b9
--- /dev/null
+++ b/chromium/components/payments/content/android/java/res/layout/payment_request_dropdown_item.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2016 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:id="@+id/spinner_item"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="24dp"
+ android:paddingEnd="24dp"
+ android:minHeight="48dp"
+ style="?android:attr/spinnerDropDownItemStyle" />
+
+</FrameLayout> \ No newline at end of file
diff --git a/chromium/components/payments/content/android/java/res/layout/payment_request_editor.xml b/chromium/components/payments/content/android/java/res/layout/payment_request_editor.xml
new file mode 100644
index 00000000000..65f225edfdd
--- /dev/null
+++ b/chromium/components/payments/content/android/java/res/layout/payment_request_editor.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2016 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. -->
+
+<!-- PaymentRequestUI editor dialog. -->
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/editor_container"
+ android:background="@color/default_bg_color">
+
+ <!-- Toolbar -->
+ <org.chromium.chrome.browser.autofill.prefeditor.EditorDialogToolbar
+ android:id="@+id/action_bar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentStart="true"
+ android:layout_alignParentEnd="true" />
+
+ <include layout="@layout/settings_action_bar_shadow" />
+
+ <!-- All the page content in scrollable form. -->
+ <org.chromium.components.browser_ui.widget.FadingEdgeScrollView
+ android:id="@+id/scroll_view"
+ android:layout_height="0dp"
+ android:layout_width="match_parent"
+ android:layout_below="@id/action_bar"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentStart="true"
+ android:layout_alignParentEnd="true" >
+
+ <LinearLayout
+ android:id="@+id/contents"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="@dimen/pref_autofill_content_spacing"
+ android:paddingEnd="@dimen/pref_autofill_content_spacing"
+ android:orientation="vertical" />
+
+ </org.chromium.components.browser_ui.widget.FadingEdgeScrollView>
+
+</RelativeLayout>
diff --git a/chromium/components/payments/content/android/java/res/layout/payment_request_editor_dropdown.xml b/chromium/components/payments/content/android/java/res/layout/payment_request_editor_dropdown.xml
new file mode 100644
index 00000000000..4dd75f2ba64
--- /dev/null
+++ b/chromium/components/payments/content/android/java/res/layout/payment_request_editor_dropdown.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2016 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. -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/editor_dialog_section_small_spacing"
+ android:layout_marginBottom="@dimen/editor_dialog_section_large_spacing"
+ android:layout_marginStart="@dimen/pref_autofill_field_horizontal_padding"
+ android:layout_marginEnd="@dimen/pref_autofill_field_horizontal_padding"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/spinner_label"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.TextSmall.Secondary" />
+
+ <androidx.appcompat.widget.AppCompatSpinner
+ android:id="@+id/spinner"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/pref_autofill_field_top_margin"
+ android:padding="0dp"
+ android:dropDownWidth="match_parent" />
+
+ <View android:id="@+id/spinner_underline" style="@style/PreferenceSpinnerUnderlineView" />
+
+ <TextView
+ android:id="@+id/spinner_error"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/pref_autofill_field_top_margin"
+ android:visibility="gone"
+ android:textAppearance="@style/TextAppearance.ErrorCaption" />
+
+</LinearLayout>
diff --git a/chromium/components/payments/content/android/java/res/layout/payment_request_editor_label.xml b/chromium/components/payments/content/android/java/res/layout/payment_request_editor_label.xml
new file mode 100644
index 00000000000..ad7fab13cd4
--- /dev/null
+++ b/chromium/components/payments/content/android/java/res/layout/payment_request_editor_label.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2016 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. -->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/editor_dialog_section_large_spacing"
+ android:layout_marginBottom="@dimen/editor_dialog_section_large_spacing"
+ android:layout_marginStart="@dimen/pref_autofill_field_horizontal_padding"
+ android:layout_marginEnd="@dimen/pref_autofill_field_horizontal_padding">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentEnd="true"
+ android:layout_centerVertical="true"
+ android:importantForAccessibility="no" />
+
+ <TextView
+ android:id="@+id/top_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_toStartOf="@id/icon"
+ android:layout_alignParentStart="true"
+ android:layout_alignParentTop="true"
+ style="@style/PreferenceTitle" />
+
+ <TextView
+ android:id="@+id/mid_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/top_label"
+ android:layout_toStartOf="@id/icon"
+ android:layout_alignParentStart="true"
+ style="@style/PreferenceSummary" />
+
+ <TextView
+ android:id="@+id/bottom_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/mid_label"
+ android:layout_toStartOf="@id/icon"
+ android:layout_alignParentStart="true"
+ android:textAppearance="@style/TextAppearance.TextSmall.Secondary" />
+
+</RelativeLayout> \ No newline at end of file
diff --git a/chromium/components/payments/content/android/java/res/layout/payment_request_header.xml b/chromium/components/payments/content/android/java/res/layout/payment_request_header.xml
new file mode 100644
index 00000000000..581ab28d2c6
--- /dev/null
+++ b/chromium/components/payments/content/android/java/res/layout/payment_request_header.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2016 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. -->
+<!-- Header containing information about the site.
+ Java code inflating this layout manages the hiding and adjustment of elements in the layout.
+ Request dialog: Displays an X in the top right corner, allowing the user to close it.
+ Result dialog: Displays no X. Title goes all the way to the end.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <org.chromium.chrome.browser.payments.ui.PaymentRequestHeader
+ android:id="@+id/header"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:minHeight="64dp">
+ <ImageView
+ android:id="@+id/icon_view"
+ android:layout_height="24dp"
+ android:layout_width="24dp"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="16dp"
+ android:layout_gravity="start|center_vertical"
+ android:importantForAccessibility="no"
+ android:scaleType="centerInside" />
+ <LinearLayout
+ android:id="@+id/page_info"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_marginStart="50dp"
+ android:layout_marginEnd="50dp"
+ android:layout_marginBottom="@dimen/payments_section_vertical_spacing"
+ android:layout_marginTop="@dimen/payments_section_vertical_spacing"
+ android:layout_gravity="center_vertical"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/page_title"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:paddingStart="6dp"
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:singleLine="true"
+ android:textAppearance="@style/TextAppearance.PaymentRequestHeaderTitle" />
+ <TextView
+ android:id="@+id/hostname"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:paddingStart="6dp"
+ android:gravity="center_vertical"
+ android:ellipsize="start"
+ android:maxLines="1"
+ android:singleLine="true"
+ android:textAppearance="@style/TextAppearance.TextMedium.Secondary" />
+ </LinearLayout>
+ <ImageView
+ android:id="@+id/close_button"
+ android:layout_gravity="end|center_vertical"
+ android:layout_height="56dp"
+ android:layout_width="56dp"
+ android:src="@drawable/btn_close"
+ android:contentDescription="@string/close"
+ android:background="?attr/selectableItemBackground"
+ android:scaleType="center"
+ app:tint="@color/default_icon_color_tint_list" />
+ </org.chromium.chrome.browser.payments.ui.PaymentRequestHeader>
+</merge>
diff --git a/chromium/components/payments/content/android/java/res/layout/payment_request_spinny.xml b/chromium/components/payments/content/android/java/res/layout/payment_request_spinny.xml
new file mode 100644
index 00000000000..bd96a3da115
--- /dev/null
+++ b/chromium/components/payments/content/android/java/res/layout/payment_request_spinny.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2016 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. -->
+
+<!-- Indeterminate spinny used to show that things are being loaded in the PaymentRequestUi.
+ Margins in this file are assigned so that it can be either included or inflated and have
+ the correct margins for the situation. When included in the main bottom sheet, the
+ FrameLayout's margins are applied, but they are ignored when this is inflated without
+ a parent.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:ignore="MergeRootFrame"
+ android:id="@+id/payment_request_spinny"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/editor_dialog_section_large_spacing" >
+
+ <!-- Indeterminate spinny to show that things are processing. -->
+ <ProgressBar
+ android:id="@+id/waiting_progress"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_marginTop="28dp"
+ android:layout_gravity="center_horizontal|top" />
+
+ <!-- Message displayed to the user.
+ The top margin is computed assuming there's a 28dp margin above and below the progress bar,
+ which is itself 24dp tall (28 + 28 + 24 = 80).
+ -->
+ <TextView
+ android:id="@+id/message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginTop="80dp"
+ android:layout_marginStart="@dimen/editor_dialog_section_large_spacing"
+ android:layout_marginEnd="@dimen/editor_dialog_section_large_spacing"
+ android:layout_marginBottom="@dimen/editor_dialog_section_large_spacing"
+ android:gravity="center_horizontal"
+ android:textAppearance="@style/TextAppearance.TextLarge.Secondary" />
+
+</FrameLayout>
diff --git a/chromium/components/payments/content/android/java/res/layout/payments_request_editor_textview.xml b/chromium/components/payments/content/android/java/res/layout/payments_request_editor_textview.xml
new file mode 100644
index 00000000000..fb9ec39b7ed
--- /dev/null
+++ b/chromium/components/payments/content/android/java/res/layout/payments_request_editor_textview.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2016 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. -->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/editor_dialog_section_small_spacing"
+ android:layout_marginBottom="@dimen/editor_dialog_section_small_spacing">
+
+ <com.google.android.material.textfield.TextInputLayout
+ android:id="@+id/text_input_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:errorTextAppearance="@style/TextAppearance.ErrorCaption">
+
+ <!-- TODO(crbug.com/900912): Fix and remove lint ignore -->
+ <AutoCompleteTextView
+ tools:ignore="LabelFor"
+ android:id="@+id/text_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:imeOptions="flagNoExtractUi" />
+ </com.google.android.material.textfield.TextInputLayout>
+
+ <LinearLayout
+ android:id="@+id/icons_layer"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:layout_gravity="end"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/value_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="24dp"
+ android:layout_gravity="center_vertical"
+ android:layout_marginStart="8dp"
+ android:adjustViewBounds="true"
+ tools:ignore="ContentDescription"
+ android:clickable="false"
+ android:visibility="gone"/>
+
+ <ImageView
+ android:id="@+id/action_icon"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:padding="12dp"
+ tools:ignore="ContentDescription"
+ android:visibility="gone"/>
+ </LinearLayout>
+</FrameLayout>
diff --git a/chromium/components/payments/content/android/java/res/values-sw600dp/dimens.xml b/chromium/components/payments/content/android/java/res/values-sw600dp/dimens.xml
new file mode 100644
index 00000000000..fe8914de7bf
--- /dev/null
+++ b/chromium/components/payments/content/android/java/res/values-sw600dp/dimens.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2020 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. -->
+
+<resources>
+ <!-- Payments UI -->
+ <dimen name="payments_ui_max_dialog_width">600dp</dimen>
+</resources>
diff --git a/chromium/components/payments/content/android/java/res/values/dimens.xml b/chromium/components/payments/content/android/java/res/values/dimens.xml
new file mode 100644
index 00000000000..692b2a344ee
--- /dev/null
+++ b/chromium/components/payments/content/android/java/res/values/dimens.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2020 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. -->
+
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <!-- Payments UI
+ * payments_section_checking_spacing:
+ The spec says that the "Checking" text should be 32dp above the bottom of its section.
+ We improvise by using 6dp because sections also have a 10dp padding, so we end up with
+ a 16dp bottom margin on the "Checking" text + 10dp section padding + 6dp leftover.
+ -->
+ <dimen name="payments_section_vertical_spacing">10dp</dimen>
+ <dimen name="payments_section_checking_spacing">6dp</dimen>
+ <dimen name="payments_section_descriptive_item_spacing">40dp</dimen>
+ <dimen name="payments_section_add_button_height">48dp</dimen>
+ <dimen name="payments_section_dropdown_top_padding">5dp</dimen>
+ <dimen name="payments_ui_max_dialog_width">0dp</dimen>
+ <dimen name="payments_ui_translation">100dp</dimen>
+ <dimen name="payments_favicon_size">24dp</dimen>
+ <dimen name="payments_handler_window_minimum_height">500dp</dimen>
+ <dimen name="payments_request_bottom_bar_vertical_padding">10dp</dimen>
+ <dimen name="payments_request_bottom_bar_horizontal_padding">16dp</dimen>
+
+ <!-- Payment minimal UI spacing -->
+ <dimen name="payment_minimal_ui_amount_currency_spacing">4dp</dimen>
+ <dimen name="payment_minimal_ui_app_icon_bottom_spacing">20dp</dimen>
+ <dimen name="payment_minimal_ui_app_icon_horizontal_spacing">10dp</dimen>
+ <dimen name="payment_minimal_ui_content_bottom_spacing">26dp</dimen>
+ <dimen name="payment_minimal_ui_content_icon_spacing">8dp</dimen>
+ <dimen name="payment_minimal_ui_content_top_spacing">78dp</dimen>
+ <dimen name="payment_minimal_ui_drag_handle_spacing">8dp</dimen>
+ <dimen name="payment_minimal_ui_large_text_vertical_spacing">8dp</dimen>
+ <dimen name="payment_minimal_ui_spacing">16dp</dimen>
+ <dimen name="payment_minimal_ui_toolbar_small_status_text_top_spacing">2dp</dimen>
+
+ <!-- Payment minimal UI element sizes -->
+ <dimen name="payment_minimal_ui_content_icon_size">56dp</dimen>
+ <dimen name="payment_minimal_ui_separator_width">0.5dp</dimen>
+ <dimen name="payment_minimal_ui_toolbar_icon_size">36dp</dimen>
+</resources>
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/BrowserPaymentRequest.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/BrowserPaymentRequest.java
new file mode 100644
index 00000000000..2132ce27cbf
--- /dev/null
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/BrowserPaymentRequest.java
@@ -0,0 +1,98 @@
+// Copyright 2020 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.
+
+package org.chromium.components.payments;
+
+import androidx.annotation.Nullable;
+
+import org.chromium.content_public.browser.RenderFrameHost;
+import org.chromium.payments.mojom.PaymentDetails;
+import org.chromium.payments.mojom.PaymentMethodData;
+import org.chromium.payments.mojom.PaymentOptions;
+import org.chromium.payments.mojom.PaymentRequest;
+import org.chromium.payments.mojom.PaymentValidationErrors;
+
+/**
+ * The browser part of the PaymentRequest implementation. The browser here can be either the
+ * Android Chrome browser or the WebLayer "browser".
+ */
+public interface BrowserPaymentRequest {
+ /** The factory that creates an instance of {@link BrowserPaymentRequest}. */
+ interface Factory {
+ /**
+ * Create an instance of {@link BrowserPaymentRequest}.
+ * @param renderFrameHost The RenderFrameHost of the merchant page.
+ * @param componentPaymentRequestImpl The ComponentPaymentRequestImpl to work together with
+ * the BrowserPaymentRequest instance.
+ * @param isOffTheRecord Whether the merchant page is in an OffTheRecord (e.g., incognito,
+ * guest mode) Tab.
+ * @return An instance of BrowserPaymentRequest, cannot be null.
+ */
+ BrowserPaymentRequest createBrowserPaymentRequest(RenderFrameHost renderFrameHost,
+ ComponentPaymentRequestImpl componentPaymentRequestImpl, boolean isOffTheRecord);
+ }
+
+ /**
+ * Initialize the browser part of the {@link PaymentRequest} implementation and validate the raw
+ * payment request data coming from the untrusted mojo.
+ * @param methodData The supported methods specified by the merchant.
+ * @param details The payment details specified by the merchant.
+ * @param options The payment options specified by the merchant, can be null.
+ * @param googlePayBridgeEligible True when the renderer process deems the current request
+ * eligible for the skip-to-GPay experimental flow. It is ultimately up to the browser
+ * process to determine whether to trigger it
+ * @return whether the initialization is successful.
+ */
+ boolean initAndValidate(PaymentMethodData[] methodData, PaymentDetails details,
+ @Nullable PaymentOptions options, boolean googlePayBridgeEligible);
+
+ /**
+ * The browser part of the {@link PaymentRequest#show} implementation.
+ * @param isUserGesture Whether this method is triggered from a user gesture.
+ * @param waitForUpdatedDetails Whether to wait for updated details. It's true when merchant
+ * passed in a promise into PaymentRequest.show(), so Chrome should disregard the
+ * initial payment details and show a spinner until the promise resolves with the
+ * correct payment details.
+ */
+ void show(boolean isUserGesture, boolean waitForUpdatedDetails);
+
+ /**
+ * The browser part of the {@link PaymentRequest#updateWith} implementation.
+ * @param details The details that the merchant provides to update the payment request.
+ */
+ void updateWith(PaymentDetails details);
+
+ /** The browser part of the {@link PaymentRequest#onPaymentDetailsNotUpdated} implementation. */
+ void onPaymentDetailsNotUpdated();
+
+ /** The browser part of the {@link PaymentRequest#abort} implementation. */
+ void abort();
+
+ /** The browser part of the {@link PaymentRequest#complete} implementation. */
+ void complete(int result);
+
+ /**
+ * The browser part of the {@link PaymentRequest#retry} implementation.
+ * @param errors The merchant-defined error message strings, which are used to indicate to the
+ * end-user that something is wrong with the data of the payment response.
+ */
+ void retry(PaymentValidationErrors errors);
+
+ /**
+ * The browser part of the {@link PaymentRequest#hasEnrolledInstrument} implementation.
+ */
+ void hasEnrolledInstrument();
+
+ /** The browser part of the {@link PaymentRequest#canMakePayment} implementation. */
+ void canMakePayment();
+
+ /** Delegate to the same method of PaymentRequestImpl. */
+ void disconnectFromClientWithDebugMessage(String debugMessage);
+
+ /**
+ * Close this instance. The callers of this method should stop referencing this instance upon
+ * calling it. This method can be called within itself without causing infinite loop.
+ */
+ void close();
+}
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/CanMakePaymentQuery.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/CanMakePaymentQuery.java
new file mode 100644
index 00000000000..fb3f28a7fad
--- /dev/null
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/CanMakePaymentQuery.java
@@ -0,0 +1,53 @@
+// 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.
+
+package org.chromium.components.payments;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.NativeMethods;
+import org.chromium.content_public.browser.WebContents;
+import org.chromium.payments.mojom.PaymentMethodData;
+
+import java.util.Map;
+
+/** Checks whether canMakePayment() can be queried. */
+@JNINamespace("payments")
+public class CanMakePaymentQuery {
+ /**
+ * Checks whether the given canMakePayment() query is allowed.
+ *
+ * @param webContents The web contents where the query is being performed.
+ * @param topLevelOrigin The top level origin using the Payment Request API.
+ * @param frameOrigin The frame origin using the Payment Request API.
+ * @param query The payment method identifiers and payment method specific data.
+ *
+ * @return True if the given query for canMakePayment() is allowed.
+ */
+ public static boolean canQuery(WebContents webContents, String topLevelOrigin,
+ String frameOrigin, Map<String, PaymentMethodData> query) {
+ return CanMakePaymentQueryJni.get().canQuery(
+ webContents, topLevelOrigin, frameOrigin, query);
+ }
+
+ @CalledByNative
+ private static String[] getMethodIdentifiers(Map<String, PaymentMethodData> query) {
+ return query.keySet().toArray(new String[query.size()]);
+ }
+
+ @CalledByNative
+ private static String getStringifiedMethodData(
+ Map<String, PaymentMethodData> query, String methodIdentifier) {
+ assert query.containsKey(methodIdentifier);
+ return query.get(methodIdentifier).stringifiedData;
+ }
+
+ private CanMakePaymentQuery() {} // Do not instantiate.
+
+ @NativeMethods
+ interface Natives {
+ boolean canQuery(WebContents webContents, String topLevelOrigin, String frameOrigin,
+ Map<String, PaymentMethodData> query);
+ }
+}
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/ComponentPaymentRequestImpl.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/ComponentPaymentRequestImpl.java
new file mode 100644
index 00000000000..b1713acb177
--- /dev/null
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/ComponentPaymentRequestImpl.java
@@ -0,0 +1,567 @@
+// Copyright 2020 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.
+
+package org.chromium.components.payments;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import org.chromium.base.Log;
+import org.chromium.components.autofill.EditableOption;
+import org.chromium.content_public.browser.RenderFrameHost;
+import org.chromium.content_public.browser.WebContents;
+import org.chromium.content_public.browser.WebContentsStatics;
+import org.chromium.mojo.system.MojoException;
+import org.chromium.payments.mojom.PayerDetail;
+import org.chromium.payments.mojom.PaymentAddress;
+import org.chromium.payments.mojom.PaymentDetails;
+import org.chromium.payments.mojom.PaymentItem;
+import org.chromium.payments.mojom.PaymentMethodData;
+import org.chromium.payments.mojom.PaymentOptions;
+import org.chromium.payments.mojom.PaymentRequest;
+import org.chromium.payments.mojom.PaymentRequestClient;
+import org.chromium.payments.mojom.PaymentResponse;
+import org.chromium.payments.mojom.PaymentValidationErrors;
+
+import java.util.List;
+
+/**
+ * {@link ComponentPaymentRequestImpl}, {@link MojoPaymentRequestGateKeeper} and PaymentRequestImpl
+ * together make up the PaymentRequest service defined in
+ * third_party/blink/public/mojom/payments/payment_request.mojom. This class provides the parts
+ * shareable between Clank and WebLayer. The Clank specific logic lives in
+ * org.chromium.chrome.browser.payments.PaymentRequestImpl.
+ * TODO(crbug.com/1102522): PaymentRequestImpl is under refactoring, with the purpose of moving the
+ * business logic of PaymentRequestImpl into ComponentPaymentRequestImpl and eventually moving
+ * PaymentRequestImpl. Note that the callers of the instances of this class need to close them with
+ * {@link ComponentPaymentRequestImpl#close()}, after which no usage is allowed.
+ */
+public class ComponentPaymentRequestImpl {
+ private static final String TAG = "CompPaymentRequest";
+ private static PaymentRequestServiceObserverForTest sObserverForTest;
+ private static NativeObserverForTest sNativeObserverForTest;
+ private final Runnable mOnClosedListener;
+ private final WebContents mWebContents;
+ private final JourneyLogger mJourneyLogger;
+ private boolean mSkipUiForNonUrlPaymentMethodIdentifiers;
+ private PaymentRequestLifecycleObserver mPaymentRequestLifecycleObserver;
+ private boolean mHasClosed;
+
+ // mClient is null only when it has closed.
+ private PaymentRequestClient mClient;
+
+ // mBrowserPaymentRequest is null when it has closed or is uninitiated.
+ private BrowserPaymentRequest mBrowserPaymentRequest;
+
+ /**
+ * An observer interface injected when running tests to allow them to observe events.
+ * This interface holds events that should be passed back to the native C++ test
+ * harness and mirrors the C++ PaymentRequest::ObserverForTest() interface. Its methods
+ * should be called in the same places that the C++ PaymentRequest object will call its
+ * ObserverForTest.
+ */
+ public interface NativeObserverForTest {
+ void onCanMakePaymentCalled();
+ void onCanMakePaymentReturned();
+ void onHasEnrolledInstrumentCalled();
+ void onHasEnrolledInstrumentReturned();
+ void onAppListReady(@Nullable List<EditableOption> paymentApps, PaymentItem total);
+ void onNotSupportedError();
+ void onConnectionTerminated();
+ void onAbortCalled();
+ void onCompleteCalled();
+ void onMinimalUIReady();
+ }
+
+ /**
+ * A test-only observer for the PaymentRequest service implementation.
+ */
+ public interface PaymentRequestServiceObserverForTest {
+ /**
+ * Called after an instance of {@link ComponentPaymentRequestImpl} has been created.
+ *
+ * @param componentPaymentRequest The newly created instance of ComponentPaymentRequestImpl.
+ */
+ void onPaymentRequestCreated(ComponentPaymentRequestImpl componentPaymentRequest);
+
+ /**
+ * Called when an abort request was denied.
+ */
+ void onPaymentRequestServiceUnableToAbort();
+
+ /**
+ * Called when the controller is notified of billing address change, but does not alter the
+ * editor UI.
+ */
+ void onPaymentRequestServiceBillingAddressChangeProcessed();
+
+ /**
+ * Called when the controller is notified of an expiration month change.
+ */
+ void onPaymentRequestServiceExpirationMonthChange();
+
+ /**
+ * Called when a show request failed. This can happen when:
+ * <ul>
+ * <li>The merchant requests only unsupported payment methods.</li>
+ * <li>The merchant requests only payment methods that don't have corresponding apps and
+ * are not able to add a credit card from PaymentRequest UI.</li>
+ * </ul>
+ */
+ void onPaymentRequestServiceShowFailed();
+
+ /**
+ * Called when the canMakePayment() request has been responded to.
+ */
+ void onPaymentRequestServiceCanMakePaymentQueryResponded();
+
+ /**
+ * Called when the hasEnrolledInstrument() request has been responded to.
+ */
+ void onPaymentRequestServiceHasEnrolledInstrumentQueryResponded();
+
+ /**
+ * Called when the payment response is ready.
+ */
+ void onPaymentResponseReady();
+
+ /**
+ * Called when the browser acknowledges the renderer's complete call, which indicates that
+ * the browser UI has closed.
+ */
+ void onCompleteReplied();
+
+ /**
+ * Called when the renderer is closing the mojo connection (e.g. upon show promise
+ * rejection).
+ */
+ void onRendererClosedMojoConnection();
+ }
+
+ /**
+ * Create an instance of {@link PaymentRequest} that provides the Android PaymentRequest
+ * service.
+ * @param renderFrameHost The RenderFrameHost of the merchant page.
+ * @param isOffTheRecord Whether the merchant page is in a off-the-record (e.g., incognito,
+ * guest mode) Tab.
+ * @param skipUiForBasicCard True if the PaymentRequest UI should be skipped when the request
+ * only supports basic-card methods.
+ * @param browserPaymentRequestFactory The factory that generates BrowserPaymentRequest.
+ * @return The created instance.
+ */
+ public static PaymentRequest createPaymentRequest(RenderFrameHost renderFrameHost,
+ boolean isOffTheRecord, boolean skipUiForBasicCard,
+ BrowserPaymentRequest.Factory browserPaymentRequestFactory) {
+ return new MojoPaymentRequestGateKeeper(
+ (client, methodData, details, options, googlePayBridgeEligible, onClosedListener)
+ -> ComponentPaymentRequestImpl.createIfParamsValid(renderFrameHost,
+ isOffTheRecord, skipUiForBasicCard, browserPaymentRequestFactory,
+ client, methodData, details, options, googlePayBridgeEligible,
+ onClosedListener));
+ }
+
+ /**
+ * @return An instance of {@link ComponentPaymentRequestImpl} only if the parameters are deemed
+ * valid; Otherwise, null.
+ */
+ @Nullable
+ private static ComponentPaymentRequestImpl createIfParamsValid(RenderFrameHost renderFrameHost,
+ boolean isOffTheRecord, boolean skipUiForBasicCard,
+ BrowserPaymentRequest.Factory browserPaymentRequestFactory,
+ @Nullable PaymentRequestClient client, @Nullable PaymentMethodData[] methodData,
+ @Nullable PaymentDetails details, @Nullable PaymentOptions options,
+ boolean googlePayBridgeEligible, Runnable onClosedListener) {
+ assert renderFrameHost != null;
+ assert browserPaymentRequestFactory != null;
+ assert onClosedListener != null;
+
+ WebContents webContents = WebContentsStatics.fromRenderFrameHost(renderFrameHost);
+ if (webContents == null || webContents.isDestroyed()) {
+ abortBeforeInstantiation(/*client=*/null, /*journeyLogger=*/null,
+ ErrorStrings.NO_WEB_CONTENTS, AbortReason.INVALID_DATA_FROM_RENDERER);
+ return null;
+ }
+
+ JourneyLogger journeyLogger = new JourneyLogger(isOffTheRecord, webContents);
+
+ if (client == null) {
+ abortBeforeInstantiation(/*client=*/null, journeyLogger, ErrorStrings.INVALID_STATE,
+ AbortReason.INVALID_DATA_FROM_RENDERER);
+ return null;
+ }
+
+ if (!OriginSecurityChecker.isOriginSecure(webContents.getLastCommittedUrl())) {
+ abortBeforeInstantiation(client, journeyLogger, ErrorStrings.NOT_IN_A_SECURE_ORIGIN,
+ AbortReason.INVALID_DATA_FROM_RENDERER);
+ return null;
+ }
+
+ if (methodData == null) {
+ abortBeforeInstantiation(client, journeyLogger,
+ ErrorStrings.INVALID_PAYMENT_METHODS_OR_DATA,
+ AbortReason.INVALID_DATA_FROM_RENDERER);
+ return null;
+ }
+
+ if (details == null) {
+ abortBeforeInstantiation(client, journeyLogger, ErrorStrings.INVALID_PAYMENT_DETAILS,
+ AbortReason.INVALID_DATA_FROM_RENDERER);
+ return null;
+ }
+
+ ComponentPaymentRequestImpl instance = new ComponentPaymentRequestImpl(client,
+ renderFrameHost, webContents, journeyLogger, skipUiForBasicCard, onClosedListener);
+ instance.onCreated();
+ boolean valid = instance.initAndValidate(renderFrameHost, browserPaymentRequestFactory,
+ methodData, details, options, googlePayBridgeEligible, isOffTheRecord);
+ if (!valid) {
+ instance.close();
+ return null;
+ }
+ return instance;
+ }
+
+ private void onCreated() {
+ if (sObserverForTest == null) return;
+ sObserverForTest.onPaymentRequestCreated(this);
+ }
+
+ /** Abort the request, used before this class's instantiation. */
+ private static void abortBeforeInstantiation(@Nullable PaymentRequestClient client,
+ @Nullable JourneyLogger journeyLogger, String debugMessage, int reason) {
+ Log.d(TAG, debugMessage);
+ if (client != null) client.onError(reason, debugMessage);
+ if (journeyLogger != null) journeyLogger.setAborted(reason);
+ if (sNativeObserverForTest != null) sNativeObserverForTest.onConnectionTerminated();
+ }
+
+ private ComponentPaymentRequestImpl(PaymentRequestClient client,
+ RenderFrameHost renderFrameHost, WebContents webContents, JourneyLogger journeyLogger,
+ boolean skipUiForBasicCard, Runnable onClosedListener) {
+ assert client != null;
+ assert renderFrameHost != null;
+ assert webContents != null;
+ assert journeyLogger != null;
+
+ mSkipUiForNonUrlPaymentMethodIdentifiers = skipUiForBasicCard;
+ mClient = client;
+ mWebContents = webContents;
+ mJourneyLogger = journeyLogger;
+ mOnClosedListener = onClosedListener;
+ mHasClosed = false;
+ }
+
+ /**
+ * Set a native-side observer for PaymentRequest implementations. This observer should be set
+ * before PaymentRequest implementations are instantiated.
+ * @param nativeObserverForTest The native-side observer.
+ */
+ @VisibleForTesting
+ public static void setNativeObserverForTest(NativeObserverForTest nativeObserverForTest) {
+ sNativeObserverForTest = nativeObserverForTest;
+ }
+
+ /** @return Get the native=side observer, for testing purpose only. */
+ @Nullable
+ public static NativeObserverForTest getNativeObserverForTest() {
+ return sNativeObserverForTest;
+ }
+
+ private boolean initAndValidate(RenderFrameHost renderFrameHost,
+ BrowserPaymentRequest.Factory factory, @Nullable PaymentMethodData[] methodData,
+ @Nullable PaymentDetails details, @Nullable PaymentOptions options,
+ boolean googlePayBridgeEligible, boolean isOffTheRecord) {
+ mBrowserPaymentRequest =
+ factory.createBrowserPaymentRequest(renderFrameHost, this, isOffTheRecord);
+ return mBrowserPaymentRequest.initAndValidate(
+ methodData, details, options, googlePayBridgeEligible);
+ }
+
+ /**
+ * The component part of the {@link PaymentRequest#show} implementation. Check {@link
+ * PaymentRequest#show} for the parameters' specification.
+ */
+ /* package */ void show(boolean isUserGesture, boolean waitForUpdatedDetails) {
+ // Every caller should stop referencing this class once close() is called.
+ assert mBrowserPaymentRequest != null;
+
+ mBrowserPaymentRequest.show(isUserGesture, waitForUpdatedDetails);
+ }
+
+ /**
+ * The component part of the {@link PaymentRequest#updateWith} implementation.
+ * @param details The details that the merchant provides to update the payment request.
+ */
+ /* package */ void updateWith(PaymentDetails details) {
+ // Every caller should stop referencing this class once close() is called.
+ assert mBrowserPaymentRequest != null;
+
+ mBrowserPaymentRequest.updateWith(details);
+ }
+
+ /**
+ * The component part of the {@link PaymentRequest#onPaymentDetailsNotUpdated} implementation.
+ */
+ /* package */ void onPaymentDetailsNotUpdated() {
+ // Every caller should stop referencing this class once close() is called.
+ assert mBrowserPaymentRequest != null;
+
+ mBrowserPaymentRequest.onPaymentDetailsNotUpdated();
+ }
+
+ /** The component part of the {@link PaymentRequest#abort} implementation. */
+ /* package */ void abort() {
+ // Every caller should stop referencing this class once close() is called.
+ assert mBrowserPaymentRequest != null;
+ mBrowserPaymentRequest.abort();
+ }
+
+ /** The component part of the {@link PaymentRequest#complete} implementation. */
+ /* package */ void complete(int result) {
+ // Every caller should stop referencing this class once close() is called.
+ assert mBrowserPaymentRequest != null;
+
+ mBrowserPaymentRequest.complete(result);
+ }
+
+ /**
+ * The component part of the {@link PaymentRequest#retry} implementation. Check {@link
+ * PaymentRequest#retry} for the parameters' specification.
+ */
+ /* package */ void retry(PaymentValidationErrors errors) {
+ // Every caller should stop referencing this class once close() is called.
+ assert mBrowserPaymentRequest != null;
+
+ mBrowserPaymentRequest.retry(errors);
+ }
+
+ /** The component part of the {@link PaymentRequest#canMakePayment} implementation. */
+ /* package */ void canMakePayment() {
+ // Every caller should stop referencing this class once close() is called.
+ assert mBrowserPaymentRequest != null;
+
+ mBrowserPaymentRequest.canMakePayment();
+ }
+
+ /**
+ * The component part of the {@link PaymentRequest#hasEnrolledInstrument} implementation.
+ */
+ /* package */ void hasEnrolledInstrument() {
+ // Every caller should stop referencing this class once close() is called.
+ assert mBrowserPaymentRequest != null;
+
+ mBrowserPaymentRequest.hasEnrolledInstrument();
+ }
+
+ /**
+ * Implement {@link PaymentRequest#close}. This should be called by the renderer only. The
+ * closing triggered by other classes should call {@link #close} instead. The caller should
+ * stop referencing this class after calling this method.
+ */
+ /* package */ void closeByRenderer() {
+ // Every caller should stop referencing this class once close() is called.
+ assert mBrowserPaymentRequest != null;
+
+ mJourneyLogger.setAborted(AbortReason.MOJO_RENDERER_CLOSING);
+ close();
+ if (sObserverForTest != null) {
+ sObserverForTest.onRendererClosedMojoConnection();
+ }
+ if (sNativeObserverForTest != null) {
+ sNativeObserverForTest.onConnectionTerminated();
+ }
+ }
+
+ /**
+ * Called when the mojo connection with the renderer PaymentRequest has an error. The caller
+ * should stop referencing this class after calling this method.
+ * @param e The mojo exception.
+ */
+ /* package */ void onConnectionError(MojoException e) {
+ // Every caller should stop referencing this class once close() is called.
+ assert mBrowserPaymentRequest != null;
+
+ mJourneyLogger.setAborted(AbortReason.MOJO_CONNECTION_ERROR);
+ close();
+ if (sNativeObserverForTest != null) {
+ sNativeObserverForTest.onConnectionTerminated();
+ }
+ }
+
+ /**
+ * Abort the request because the (untrusted) renderer passes invalid data.
+ * @param debugMessage The debug message to be sent to the renderer.
+ */
+ /* package */ void abortForInvalidDataFromRenderer(String debugMessage) {
+ // Every caller should stop referencing this class once close() is called.
+ assert mBrowserPaymentRequest != null;
+
+ mJourneyLogger.setAborted(AbortReason.INVALID_DATA_FROM_RENDERER);
+ mBrowserPaymentRequest.disconnectFromClientWithDebugMessage(debugMessage);
+ }
+
+ /**
+ * Close this instance and release all of the retained resources. The external callers of this
+ * method should stop referencing this instance upon calling. This method can be called within
+ * itself without causing infinite loops.
+ */
+ public void close() {
+ if (mHasClosed) return;
+ mHasClosed = true;
+
+ if (mBrowserPaymentRequest == null) return;
+ mBrowserPaymentRequest.close();
+ mBrowserPaymentRequest = null;
+
+ // mClient can be null only when this method is called from
+ // ComponentPaymentRequestImpl#create().
+ if (mClient != null) mClient.close();
+ mClient = null;
+
+ mOnClosedListener.run();
+ }
+
+ /**
+ * Register an observer for the PaymentRequest lifecycle.
+ * @param paymentRequestLifecycleObserver The observer, cannot be null.
+ */
+ public void registerPaymentRequestLifecycleObserver(
+ PaymentRequestLifecycleObserver paymentRequestLifecycleObserver) {
+ assert paymentRequestLifecycleObserver != null;
+ mPaymentRequestLifecycleObserver = paymentRequestLifecycleObserver;
+ }
+
+ /** @return The observer for the PaymentRequest lifecycle, can be null. */
+ @Nullable
+ public PaymentRequestLifecycleObserver getPaymentRequestLifecycleObserver() {
+ return mPaymentRequestLifecycleObserver;
+ }
+
+ /** @return An observer for the payment request service, if any; otherwise, null. */
+ @Nullable
+ public static PaymentRequestServiceObserverForTest getObserverForTest() {
+ return sObserverForTest;
+ }
+
+ /** Set an observer for the payment request service, cannot be null. */
+ @VisibleForTesting
+ public static void setObserverForTest(PaymentRequestServiceObserverForTest observerForTest) {
+ assert observerForTest != null;
+ sObserverForTest = observerForTest;
+ }
+
+ /**
+ * @return True when skip UI is available for non-url based payment method identifiers (e.g.
+ * basic-card).
+ */
+ public boolean skipUiForNonUrlPaymentMethodIdentifiers() {
+ return mSkipUiForNonUrlPaymentMethodIdentifiers;
+ }
+
+ @VisibleForTesting
+ public void setSkipUiForNonUrlPaymentMethodIdentifiersForTest() {
+ mSkipUiForNonUrlPaymentMethodIdentifiers = true;
+ }
+
+ /** Invokes {@link PaymentRequest.onPaymentMethodChange}. */
+ public void onPaymentMethodChange(String methodName, String stringifiedDetails) {
+ // Every caller should stop referencing this class once close() is called.
+ assert mClient != null;
+
+ mClient.onPaymentMethodChange(methodName, stringifiedDetails);
+ }
+
+ /** Invokes {@link PaymentRequest.onShippingAddressChange}. */
+ public void onShippingAddressChange(PaymentAddress address) {
+ // Every caller should stop referencing this class once close() is called.
+ assert mClient != null;
+
+ mClient.onShippingAddressChange(address);
+ }
+
+ /** Invokes {@link PaymentRequest.onShippingOptionChange}. */
+ public void onShippingOptionChange(String shippingOptionId) {
+ // Every caller should stop referencing this class once close() is called.
+ assert mClient != null;
+
+ mClient.onShippingOptionChange(shippingOptionId);
+ }
+
+ /** Invokes {@link PaymentRequest.onPayerDetailChange}. */
+ public void onPayerDetailChange(PayerDetail detail) {
+ // Every caller should stop referencing this class once close() is called.
+ assert mClient != null;
+
+ mClient.onPayerDetailChange(detail);
+ }
+
+ /** Invokes {@link PaymentRequest.onPaymentResponse}. */
+ public void onPaymentResponse(PaymentResponse response) {
+ // Every caller should stop referencing this class once close() is called.
+ assert mClient != null;
+
+ mClient.onPaymentResponse(response);
+ }
+
+ /** Invokes {@link PaymentRequest.onError}. */
+ public void onError(int error, String errorMessage) {
+ // Every caller should stop referencing this class once close() is called.
+ assert mClient != null;
+
+ mClient.onError(error, errorMessage);
+ }
+
+ /** Invokes {@link PaymentRequest.onComplete}. */
+ public void onComplete() {
+ // Every caller should stop referencing this class once close() is called.
+ assert mClient != null;
+
+ mClient.onComplete();
+ }
+
+ /** Invokes {@link PaymentRequest.onAbort}. */
+ public void onAbort(boolean abortedSuccessfully) {
+ // Every caller should stop referencing this class once close() is called.
+ assert mClient != null;
+
+ mClient.onAbort(abortedSuccessfully);
+ }
+
+ /** Invokes {@link PaymentRequest.onCanMakePayment}. */
+ public void onCanMakePayment(int result) {
+ // Every caller should stop referencing this class once close() is called.
+ assert mClient != null;
+
+ mClient.onCanMakePayment(result);
+ }
+
+ /** Invokes {@link PaymentRequest.onHasEnrolledInstrument}. */
+ public void onHasEnrolledInstrument(int result) {
+ // Every caller should stop referencing this class once close() is called.
+ assert mClient != null;
+
+ mClient.onHasEnrolledInstrument(result);
+ }
+
+ /** Invokes {@link PaymentRequest.warnNoFavicon}. */
+ public void warnNoFavicon() {
+ // Every caller should stop referencing this class once close() is called.
+ assert mClient != null;
+
+ mClient.warnNoFavicon();
+ }
+
+ /**
+ * @return The logger of the user journey of the Android PaymentRequest service, cannot be
+ * null.
+ */
+ public JourneyLogger getJourneyLogger() {
+ return mJourneyLogger;
+ }
+
+ /** @return The WebContents of the merchant's page, cannot be null. */
+ public WebContents getWebContents() {
+ return mWebContents;
+ }
+}
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/JourneyLogger.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/JourneyLogger.java
index 93a29b3b056..bc519530283 100644
--- a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/JourneyLogger.java
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/JourneyLogger.java
@@ -20,7 +20,14 @@ public class JourneyLogger {
private boolean mHasRecorded;
+ /**
+ * Creates the journey logger.
+ * @param isIncognito Whether the user profile is incognito.
+ * @param webContents The web contents where PaymentRequest API is invoked. Should not be null.
+ */
public JourneyLogger(boolean isIncognito, WebContents webContents) {
+ assert webContents != null;
+ assert !webContents.isDestroyed();
// Note that this pointer could leak the native object. The called must call destroy() to
// ensure that the native object is destroyed.
mJourneyLoggerAndroid = JourneyLoggerJni.get().initJourneyLoggerAndroid(
@@ -109,9 +116,6 @@ public class JourneyLogger {
* @param event The event that occurred.
*/
public void setEventOccurred(int event) {
- assert event >= 0;
- assert event < Event.ENUM_MAX;
-
JourneyLoggerJni.get().setEventOccurred(mJourneyLoggerAndroid, JourneyLogger.this, event);
}
@@ -202,6 +206,14 @@ public class JourneyLogger {
}
/**
+ * Records that the payment request has entered the given checkout step.
+ * @param step An int indicating the step to be recorded.
+ */
+ public void recordCheckoutStep(int step) {
+ JourneyLoggerJni.get().recordCheckoutStep(mJourneyLoggerAndroid, JourneyLogger.this, step);
+ }
+
+ /**
* Records the time when request.show() is called.
*/
public void setTriggerTime() {
@@ -246,6 +258,7 @@ public class JourneyLogger {
void setNotShown(long nativeJourneyLoggerAndroid, JourneyLogger caller, int reason);
void recordTransactionAmount(long nativeJourneyLoggerAndroid, JourneyLogger caller,
String currency, String value, boolean completed);
+ void recordCheckoutStep(long nativeJourneyLoggerAndroid, JourneyLogger caller, int step);
void setTriggerTime(long nativeJourneyLoggerAndroid, JourneyLogger caller);
void setPaymentAppUkmSourceId(
long nativeJourneyLoggerAndroid, JourneyLogger caller, long sourceId);
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/MojoPaymentRequestGateKeeper.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/MojoPaymentRequestGateKeeper.java
new file mode 100644
index 00000000000..c4c500be045
--- /dev/null
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/MojoPaymentRequestGateKeeper.java
@@ -0,0 +1,147 @@
+// Copyright 2020 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.
+
+package org.chromium.components.payments;
+
+import org.chromium.mojo.system.MojoException;
+import org.chromium.payments.mojom.PaymentDetails;
+import org.chromium.payments.mojom.PaymentMethodData;
+import org.chromium.payments.mojom.PaymentOptions;
+import org.chromium.payments.mojom.PaymentRequest;
+import org.chromium.payments.mojom.PaymentRequestClient;
+import org.chromium.payments.mojom.PaymentValidationErrors;
+
+/**
+ * Guards against invalid mojo parameters and enforces correct call sequence from mojo IPC in the
+ * untrusted renderer, so ComponentPaymentRequestImpl does not have to.
+ */
+/* package */ class MojoPaymentRequestGateKeeper implements PaymentRequest {
+ private final ComponentPaymentRequestImplCreator mComponentPaymentRequestImplCreator;
+ private ComponentPaymentRequestImpl mComponentPaymentRequestImpl;
+
+ /** The creator of ComponentPaymentRequestImpl. */
+ /* package */ interface ComponentPaymentRequestImplCreator {
+ /**
+ * Create an instance of ComponentPaymentRequestImpl if the parameters are valid.
+ * @param client The client of the renderer PaymentRequest, need validation before usage.
+ * @param methodData The supported methods specified by the merchant, need validation before
+ * usage.
+ * @param details The payment details specified by the merchant, need validation before
+ * usage.
+ * @param options The payment options specified by the merchant, need validation before
+ * usage.
+ * @param googlePayBridgeEligible True when the renderer process deems the current request
+ * eligible for the skip-to-GPay experimental flow. It is ultimately up to the
+ * browser process to determine whether to trigger it.
+ * @param onClosedListener The listener that's invoked when ComponentPaymentRequestImpl has
+ * just closed.
+ * @return The created instance, if the parameters are valid; otherwise, null.
+ */
+ ComponentPaymentRequestImpl createComponentPaymentRequestImplIfParamsValid(
+ PaymentRequestClient client, PaymentMethodData[] methodData, PaymentDetails details,
+ PaymentOptions options, boolean googlePayBridgeEligible, Runnable onClosedListener);
+ }
+
+ /**
+ * Create an instance of MojoPaymentRequestGateKeeper.
+ * @param creator The creator of ComponentPaymentRequestImpl.
+ */
+ /* package */ MojoPaymentRequestGateKeeper(ComponentPaymentRequestImplCreator creator) {
+ mComponentPaymentRequestImplCreator = creator;
+ }
+
+ // Implement PaymentRequest:
+ @Override
+ public void init(PaymentRequestClient client, PaymentMethodData[] methodData,
+ PaymentDetails details, PaymentOptions options, boolean googlePayBridgeEligible) {
+ if (mComponentPaymentRequestImpl != null) {
+ mComponentPaymentRequestImpl.abortForInvalidDataFromRenderer(
+ ErrorStrings.ATTEMPTED_INITIALIZATION_TWICE);
+ mComponentPaymentRequestImpl = null;
+ return;
+ }
+
+ // Note that a null value would be assigned when the params are invalid.
+ mComponentPaymentRequestImpl =
+ mComponentPaymentRequestImplCreator.createComponentPaymentRequestImplIfParamsValid(
+ client, methodData, details, options, googlePayBridgeEligible,
+ this::onComponentPaymentRequestImplClosed);
+ }
+
+ private void onComponentPaymentRequestImplClosed() {
+ mComponentPaymentRequestImpl = null;
+ }
+
+ // Implement PaymentRequest:
+ @Override
+ public void show(boolean isUserGesture, boolean waitForUpdatedDetails) {
+ if (mComponentPaymentRequestImpl == null) return;
+ mComponentPaymentRequestImpl.show(isUserGesture, waitForUpdatedDetails);
+ }
+
+ // Implement PaymentRequest:
+ @Override
+ public void updateWith(PaymentDetails details) {
+ if (mComponentPaymentRequestImpl == null) return;
+ mComponentPaymentRequestImpl.updateWith(details);
+ }
+
+ // Implement PaymentRequest:
+ @Override
+ public void onPaymentDetailsNotUpdated() {
+ if (mComponentPaymentRequestImpl == null) return;
+ mComponentPaymentRequestImpl.onPaymentDetailsNotUpdated();
+ }
+
+ // Implement PaymentRequest:
+ @Override
+ public void abort() {
+ if (mComponentPaymentRequestImpl == null) return;
+ mComponentPaymentRequestImpl.abort();
+ }
+
+ // Implement PaymentRequest:
+ @Override
+ public void complete(int result) {
+ if (mComponentPaymentRequestImpl == null) return;
+ mComponentPaymentRequestImpl.complete(result);
+ }
+
+ // Implement PaymentRequest:
+ @Override
+ public void retry(PaymentValidationErrors errors) {
+ if (mComponentPaymentRequestImpl == null) return;
+ mComponentPaymentRequestImpl.retry(errors);
+ }
+
+ // Implement PaymentRequest:
+ @Override
+ public void canMakePayment() {
+ if (mComponentPaymentRequestImpl == null) return;
+ mComponentPaymentRequestImpl.canMakePayment();
+ }
+
+ // Implement PaymentRequest:
+ @Override
+ public void hasEnrolledInstrument() {
+ if (mComponentPaymentRequestImpl == null) return;
+ mComponentPaymentRequestImpl.hasEnrolledInstrument();
+ }
+
+ // Implement PaymentRequest:
+ @Override
+ public void close() {
+ if (mComponentPaymentRequestImpl == null) return;
+ mComponentPaymentRequestImpl.closeByRenderer();
+ mComponentPaymentRequestImpl = null;
+ }
+
+ // Implement PaymentRequest:
+ @Override
+ public void onConnectionError(MojoException e) {
+ if (mComponentPaymentRequestImpl == null) return;
+ mComponentPaymentRequestImpl.onConnectionError(e);
+ mComponentPaymentRequestImpl = null;
+ }
+}
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/OWNERS b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/OWNERS
index db4625d4570..54fab122ac4 100644
--- a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/OWNERS
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/OWNERS
@@ -1,5 +1,6 @@
# TEAM: payments-dev@chromium.org
# COMPONENT: UI>Browser>Payments
+# OS: Android
file://components/payments/OWNERS
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentApp.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentApp.java
index b719873aa8c..e465261712f 100644
--- a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentApp.java
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentApp.java
@@ -306,4 +306,12 @@ public abstract class PaymentApp extends EditableOption {
public @PaymentAppType int getPaymentAppType() {
return PaymentAppType.UNDEFINED;
}
+
+ /**
+ * @return Whether this app should be chosen over other available payment apps. For example,
+ * when the Play Billing payment app is available in a TWA.
+ */
+ public boolean isPreferred() {
+ return false;
+ }
}
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentAppFactoryParams.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentAppFactoryParams.java
new file mode 100644
index 00000000000..57a9817f783
--- /dev/null
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentAppFactoryParams.java
@@ -0,0 +1,87 @@
+// Copyright 2019 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.
+
+package org.chromium.components.payments;
+
+import androidx.annotation.Nullable;
+
+import org.chromium.content_public.browser.RenderFrameHost;
+import org.chromium.content_public.browser.WebContents;
+import org.chromium.url.Origin;
+
+/** Interface for providing information to a payment app factory. */
+public interface PaymentAppFactoryParams extends PaymentRequestParams {
+ /** @return The web contents where the payment is being requested. */
+ WebContents getWebContents();
+
+ /** @return The RenderFrameHost for the frame that initiates the payment request. */
+ RenderFrameHost getRenderFrameHost();
+
+ /** @return The PaymentRequest object identifier. */
+ default String getId() {
+ return null;
+ }
+
+ /**
+ * @return The scheme, host, and port of the last committed URL of the top-level context as
+ * formatted by UrlFormatter.formatUrlForSecurityDisplay().
+ */
+ default String getTopLevelOrigin() {
+ return null;
+ }
+
+ /**
+ * @return The scheme, host, and port of the last committed URL of the iframe that invoked the
+ * PaymentRequest API as formatted by UrlFormatter.formatUrlForSecurityDisplay().
+ */
+ default String getPaymentRequestOrigin() {
+ return null;
+ }
+
+ /**
+ * @return The origin of the iframe that invoked the PaymentRequest API. Can be opaque. Used by
+ * security features like 'Sec-Fetch-Site' and 'Cross-Origin-Resource-Policy'. Should not be
+ * null.
+ */
+ default Origin getPaymentRequestSecurityOrigin() {
+ return null;
+ }
+
+ /**
+ * @return The certificate chain of the top-level context as returned by
+ * CertificateChainHelper.getCertificateChain(). Can be null.
+ */
+ @Nullable
+ default byte[][] getCertificateChain() {
+ return null;
+ }
+
+ /**
+ * @return Whether crawling the web for just-in-time installable payment handlers is enabled.
+ */
+ default boolean getMayCrawl() {
+ return false;
+ }
+
+ /**
+ * @return The listener for payment method, shipping address, and shipping option change events.
+ */
+ default PaymentRequestUpdateEventListener getPaymentRequestUpdateEventListener() {
+ return null;
+ }
+
+ /** @return The Payment Request information received from the merchant. */
+ default PaymentRequestSpec getSpec() {
+ return null;
+ }
+
+ /**
+ * @return The Android package name of the Trusted Web Activity that invoked Chrome, if running
+ * in TWA mode. Otherwise null or empty string.
+ */
+ @Nullable
+ default String getTwaPackageName() {
+ return null;
+ }
+}
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentFeatureList.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentFeatureList.java
index 28bf46d2499..79b22ff47b7 100644
--- a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentFeatureList.java
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentFeatureList.java
@@ -17,6 +17,7 @@ import org.chromium.base.annotations.NativeMethods;
public class PaymentFeatureList {
/** Alphabetical: */
public static final String ANDROID_APP_PAYMENT_UPDATE_EVENTS = "AndroidAppPaymentUpdateEvents";
+ public static final String ENFORCE_FULL_DELEGATION = "EnforceFullDelegation";
public static final String PAYMENT_REQUEST_SKIP_TO_GPAY = "PaymentRequestSkipToGPay";
public static final String PAYMENT_REQUEST_SKIP_TO_GPAY_IF_NO_CARD =
"PaymentRequestSkipToGPayIfNoCard";
@@ -27,6 +28,7 @@ public class PaymentFeatureList {
public static final String WEB_PAYMENTS = "WebPayments";
public static final String WEB_PAYMENTS_ALWAYS_ALLOW_JUST_IN_TIME_PAYMENT_APP =
"AlwaysAllowJustInTimePaymentApp";
+ public static final String WEB_PAYMENTS_APP_STORE_BILLING = "AppStoreBilling";
public static final String WEB_PAYMENTS_APP_STORE_BILLING_DEBUG = "AppStoreBillingDebug";
public static final String WEB_PAYMENTS_EXPERIMENTAL_FEATURES =
"WebPaymentsExperimentalFeatures";
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentManifestWebDataService.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentManifestWebDataService.java
new file mode 100644
index 00000000000..2999c039226
--- /dev/null
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentManifestWebDataService.java
@@ -0,0 +1,151 @@
+// 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.
+
+package org.chromium.components.payments;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.NativeMethods;
+
+/** Java wrapper of the payment manifest web data service. */
+@JNINamespace("payments")
+public class PaymentManifestWebDataService {
+ /** Interface for the callback to invoke when getting data from the web data service. */
+ public interface PaymentManifestWebDataServiceCallback {
+ /**
+ * Called when getPaymentMethodManifest success.
+ *
+ * @param appIdentifiers The list of package names and origins of the supported apps in the
+ * payment method manifest. May also contain "*" to indicate that
+ * all origins are supported.
+ */
+ @CalledByNative("PaymentManifestWebDataServiceCallback")
+ void onPaymentMethodManifestFetched(String[] appIdentifiers);
+
+ /**
+ * Called when getPaymentWebAppManifest success.
+ *
+ * @param manifest The web app manifest sections.
+ */
+ @CalledByNative("PaymentManifestWebDataServiceCallback")
+ void onPaymentWebAppManifestFetched(WebAppManifestSection[] manifest);
+ }
+
+ /** Holds the native counterpart of this class object. */
+ private long mManifestWebDataServiceAndroid;
+
+ public PaymentManifestWebDataService() {
+ mManifestWebDataServiceAndroid =
+ PaymentManifestWebDataServiceJni.get().init(PaymentManifestWebDataService.this);
+ }
+
+ /**
+ * Destroy this class object. It destroys the native counterpart.
+ */
+ public void destroy() {
+ PaymentManifestWebDataServiceJni.get().destroy(
+ mManifestWebDataServiceAndroid, PaymentManifestWebDataService.this);
+ mManifestWebDataServiceAndroid = 0;
+ }
+
+ /**
+ * Gets the payment method's manifest.
+ *
+ * @param methodName The payment method name.
+ * @param callback The callback to invoke when finishing the request.
+ * @return True if the result will be returned through callback.
+ */
+ public boolean getPaymentMethodManifest(
+ String methodName, PaymentManifestWebDataServiceCallback callback) {
+ return PaymentManifestWebDataServiceJni.get().getPaymentMethodManifest(
+ mManifestWebDataServiceAndroid, PaymentManifestWebDataService.this, methodName,
+ callback);
+ }
+
+ /**
+ * Gets the corresponding payment web app's manifest.
+ *
+ * @param appPackageName The package name of the Android payment app.
+ * @param callback The callback to invoke when finishing the request.
+ * @return True if the result will be returned through callback.
+ */
+ public boolean getPaymentWebAppManifest(
+ String appPackageName, PaymentManifestWebDataServiceCallback callback) {
+ return PaymentManifestWebDataServiceJni.get().getPaymentWebAppManifest(
+ mManifestWebDataServiceAndroid, PaymentManifestWebDataService.this, appPackageName,
+ callback);
+ }
+
+ /**
+ * Adds the supported Android apps' package names of the method.
+ *
+ * @param methodName The method name.
+ * @param appPackageNames The supported app package names and origins. Also possibly "*" if
+ * applicable.
+ */
+ public void addPaymentMethodManifest(String methodName, String[] appIdentifiers) {
+ PaymentManifestWebDataServiceJni.get().addPaymentMethodManifest(
+ mManifestWebDataServiceAndroid, PaymentManifestWebDataService.this, methodName,
+ appIdentifiers);
+ }
+
+ /**
+ * Adds web app's manifest.
+ *
+ * @param manifest The manifest.
+ */
+ public void addPaymentWebAppManifest(WebAppManifestSection[] manifest) {
+ PaymentManifestWebDataServiceJni.get().addPaymentWebAppManifest(
+ mManifestWebDataServiceAndroid, PaymentManifestWebDataService.this, manifest);
+ }
+
+ @CalledByNative
+ private static WebAppManifestSection[] createManifest(int numberOfsections) {
+ return new WebAppManifestSection[numberOfsections];
+ }
+
+ @CalledByNative
+ private static void addSectionToManifest(WebAppManifestSection[] manifest, int sectionIndex,
+ String id, long minVersion, int numberOfFingerprints) {
+ manifest[sectionIndex] = new WebAppManifestSection(id, minVersion, numberOfFingerprints);
+ }
+
+ @CalledByNative
+ private static void addFingerprintToSection(WebAppManifestSection[] manifest, int sectionIndex,
+ int fingerprintIndex, byte[] fingerprint) {
+ manifest[sectionIndex].fingerprints[fingerprintIndex] = fingerprint;
+ }
+
+ @CalledByNative
+ private static String getIdFromSection(WebAppManifestSection manifestSection) {
+ return manifestSection.id;
+ }
+
+ @CalledByNative
+ private static long getMinVersionFromSection(WebAppManifestSection manifestSection) {
+ return manifestSection.minVersion;
+ }
+
+ @CalledByNative
+ private static byte[][] getFingerprintsFromSection(WebAppManifestSection manifestSection) {
+ return manifestSection.fingerprints;
+ }
+
+ @NativeMethods
+ interface Natives {
+ long init(PaymentManifestWebDataService caller);
+ void destroy(long nativePaymentManifestWebDataServiceAndroid,
+ PaymentManifestWebDataService caller);
+ boolean getPaymentMethodManifest(long nativePaymentManifestWebDataServiceAndroid,
+ PaymentManifestWebDataService caller, String methodName,
+ PaymentManifestWebDataServiceCallback callback);
+ boolean getPaymentWebAppManifest(long nativePaymentManifestWebDataServiceAndroid,
+ PaymentManifestWebDataService caller, String appPackageName,
+ PaymentManifestWebDataServiceCallback callback);
+ void addPaymentMethodManifest(long nativePaymentManifestWebDataServiceAndroid,
+ PaymentManifestWebDataService caller, String methodName, String[] appPackageNames);
+ void addPaymentWebAppManifest(long nativePaymentManifestWebDataServiceAndroid,
+ PaymentManifestWebDataService caller, WebAppManifestSection[] manifest);
+ }
+}
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentOptionsUtils.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentOptionsUtils.java
new file mode 100644
index 00000000000..de0ee8fa45c
--- /dev/null
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentOptionsUtils.java
@@ -0,0 +1,90 @@
+// Copyright 2019 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.
+
+package org.chromium.components.payments;
+
+import androidx.annotation.Nullable;
+
+import org.chromium.payments.mojom.PaymentOptions;
+
+/**
+ * A collection of utility methods for PaymentOptions.
+ */
+public class PaymentOptionsUtils {
+ /**
+ * @param options Any PaymentOption, can be null.
+ * @return Whether a PaymentOptions has requested any information (shipping, payer's email,
+ * payer's phone, payer's name).
+ */
+ public static boolean requestAnyInformation(@Nullable PaymentOptions options) {
+ if (options == null) return false;
+ return options.requestShipping || options.requestPayerEmail || options.requestPayerPhone
+ || options.requestPayerName;
+ }
+
+ /**
+ * @param options Any PaymentOption, can be null.
+ * @return Whether a PaymentOptions has requested any payer's information (email, phone, name).
+ */
+ public static boolean requestAnyContactInformation(@Nullable PaymentOptions options) {
+ if (options == null) return false;
+ return options.requestPayerEmail || options.requestPayerPhone || options.requestPayerName;
+ }
+
+ /**
+ * @param options Any PaymentOptions, can be null.
+ * @return Return a JSON string indicating whether each information is requested in the
+ * PaymentOptions.
+ */
+ public static String stringifyRequestedInformation(@Nullable PaymentOptions options) {
+ boolean requestPayerEmail = false;
+ boolean requestPayerName = false;
+ boolean requestPayerPhone = false;
+ boolean requestShipping = false;
+ if (options != null) {
+ requestPayerEmail = options.requestPayerEmail;
+ requestPayerName = options.requestPayerName;
+ requestPayerPhone = options.requestPayerPhone;
+ requestShipping = options.requestShipping;
+ }
+ return String.format("{payerEmail:%s,payerName:%s,payerPhone:%s,shipping:%s}",
+ requestPayerEmail, requestPayerName, requestPayerPhone, requestShipping);
+ }
+
+ /**
+ * @param paymentOptions The PaymentOptions of the payment request.
+ * @return Whether requestShipping is specified in the payment request.
+ */
+ public static boolean requestShipping(
+ org.chromium.payments.mojom.PaymentOptions paymentOptions) {
+ return paymentOptions != null && paymentOptions.requestShipping;
+ }
+
+ /**
+ * @param paymentOptions The PaymentOptions of the payment request.
+ * @return Whether requestPayerName is specified in the payment request.
+ */
+ public static boolean requestPayerName(
+ org.chromium.payments.mojom.PaymentOptions paymentOptions) {
+ return paymentOptions != null && paymentOptions.requestPayerName;
+ }
+
+ /**
+ * @param paymentOptions The PaymentOptions of the payment request.
+ * @return Whether requestPayerPhone is specified in the payment request.
+ */
+ public static boolean requestPayerPhone(
+ org.chromium.payments.mojom.PaymentOptions paymentOptions) {
+ return paymentOptions != null && paymentOptions.requestPayerPhone;
+ }
+
+ /**
+ * @param paymentOptions The PaymentOptions of the payment request.
+ * @return Whether requestPayerEmail is specified in the payment request.
+ */
+ public static boolean requestPayerEmail(
+ org.chromium.payments.mojom.PaymentOptions paymentOptions) {
+ return paymentOptions != null && paymentOptions.requestPayerEmail;
+ }
+}
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestLifecycleObserver.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestLifecycleObserver.java
new file mode 100644
index 00000000000..5b8a18dc28c
--- /dev/null
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestLifecycleObserver.java
@@ -0,0 +1,23 @@
+// Copyright 2020 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.
+
+package org.chromium.components.payments;
+
+import org.chromium.payments.mojom.PaymentRequest;
+import org.chromium.payments.mojom.PaymentValidationErrors;
+
+/** Observe the lifecycle of the PaymentRequest. */
+public interface PaymentRequestLifecycleObserver {
+ /**
+ * Called when all of the PaymentRequest parameters have been initiated and validated.
+ * @param params The parameters.
+ */
+ void onPaymentRequestParamsInitiated(PaymentRequestParams params);
+
+ /**
+ * Called after {@link PaymentRequest#retry} is invoked.
+ * @param errors The payment validation errors.
+ */
+ void onRetry(PaymentValidationErrors errors);
+}
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestParams.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestParams.java
new file mode 100644
index 00000000000..70e77c5eccd
--- /dev/null
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestParams.java
@@ -0,0 +1,39 @@
+// Copyright 2020 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.
+
+package org.chromium.components.payments;
+
+import org.chromium.payments.mojom.PaymentDetailsModifier;
+import org.chromium.payments.mojom.PaymentItem;
+import org.chromium.payments.mojom.PaymentMethodData;
+import org.chromium.payments.mojom.PaymentOptions;
+
+import java.util.Map;
+
+/**
+ * The parameters of PaymentRequest specified by the merchant.
+ */
+public interface PaymentRequestParams {
+ /** @return The PaymentOptions set by the merchant. */
+ PaymentOptions getPaymentOptions();
+
+ /**
+ * @return The unmodifiable mapping of method names to modifiers, which include modified totals
+ * and additional line items. Used to display modified totals for each payment app, modified
+ * total in order summary, and additional line items in order summary. Should not be null.
+ */
+ Map<String, PaymentDetailsModifier> getUnmodifiableModifiers();
+
+ /**
+ * @return The unmodifiable mapping of payment method identifier to the method-specific data in
+ * the payment request.
+ */
+ Map<String, PaymentMethodData> getMethodData();
+
+ /**
+ * @return The raw total amount being charged - the total property of the PaymentDetails of
+ * payment request.
+ */
+ PaymentItem getRawTotal();
+}
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/SslValidityChecker.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/SslValidityChecker.java
new file mode 100644
index 00000000000..0820b8d727c
--- /dev/null
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/SslValidityChecker.java
@@ -0,0 +1,47 @@
+// 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.
+
+package org.chromium.components.payments;
+
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.NativeMethods;
+import org.chromium.content_public.browser.WebContents;
+
+/** SSL validity checker. */
+@JNINamespace("payments")
+public class SslValidityChecker {
+ /**
+ * Returns a developer-facing error message for invalid SSL certificate state or an empty
+ * string when the SSL certificate is valid. Only SECURE and
+ * SECURE_WITH_POLICY_INSTALLED_CERT are considered valid for web payments, unless
+ * --ignore-certificate-errors is specified on the command line.
+ *
+ * @param webContents The web contents whose SSL certificate state will be used for the error
+ * message. Should not be null. A null |web_contents| parameter will return
+ * an "Invalid certificate" error message.
+ * @return A developer-facing error message about the SSL certificate state in the given web
+ * contents or an empty string when the SSL certificate is valid.
+ */
+ public static String getInvalidSslCertificateErrorMessage(WebContents webContents) {
+ return SslValidityCheckerJni.get().getInvalidSslCertificateErrorMessage(webContents);
+ }
+
+ /**
+ * Returns true for web contents that is allowed in a payment handler window.
+ *
+ * @param webContents The web contents to check.
+ * @return Whether the web contents is a allowed in a payment handler window.
+ */
+ public static boolean isValidPageInPaymentHandlerWindow(WebContents webContents) {
+ return SslValidityCheckerJni.get().isValidPageInPaymentHandlerWindow(webContents);
+ }
+
+ private SslValidityChecker() {}
+
+ @NativeMethods
+ interface Natives {
+ String getInvalidSslCertificateErrorMessage(WebContents webContents);
+ boolean isValidPageInPaymentHandlerWindow(WebContents webContents);
+ }
+}
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/SupportedDelegations.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/SupportedDelegations.java
new file mode 100644
index 00000000000..0844fb49f11
--- /dev/null
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/SupportedDelegations.java
@@ -0,0 +1,99 @@
+// Copyright 2019 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.
+
+package org.chromium.components.payments;
+
+import androidx.annotation.Nullable;
+
+import org.chromium.base.Log;
+
+/**
+ * This class represents the supported delegations of a service worker based payment app.
+ */
+public class SupportedDelegations {
+ private static final String TAG = "SupportedDelegations";
+ private final boolean mShippingAddress;
+ private final boolean mPayerName;
+ private final boolean mPayerPhone;
+ private final boolean mPayerEmail;
+
+ public SupportedDelegations(
+ boolean shippingAddress, boolean payerName, boolean payerPhone, boolean payerEmail) {
+ mShippingAddress = shippingAddress;
+ mPayerName = payerName;
+ mPayerPhone = payerPhone;
+ mPayerEmail = payerEmail;
+ }
+ public SupportedDelegations() {
+ mShippingAddress = false;
+ mPayerName = false;
+ mPayerPhone = false;
+ mPayerEmail = false;
+ }
+
+ public boolean providesAll(org.chromium.payments.mojom.PaymentOptions options) {
+ if (options == null) return true;
+ if (options.requestShipping && !mShippingAddress) return false;
+ if (options.requestPayerName && !mPayerName) return false;
+ if (options.requestPayerPhone && !mPayerPhone) return false;
+ if (options.requestPayerEmail && !mPayerEmail) return false;
+ return true;
+ }
+
+ public boolean getShippingAddress() {
+ return mShippingAddress;
+ }
+
+ public boolean getPayerName() {
+ return mPayerName;
+ }
+
+ public boolean getPayerPhone() {
+ return mPayerPhone;
+ }
+
+ public boolean getPayerEmail() {
+ return mPayerEmail;
+ }
+
+ public static SupportedDelegations createFromStringArray(
+ @Nullable String[] supportedDelegationsNames) throws IllegalArgumentException {
+ if (supportedDelegationsNames == null || supportedDelegationsNames.length == 0) {
+ return new SupportedDelegations();
+ }
+
+ boolean shippingAddress = false;
+ boolean payerName = false;
+ boolean payerPhone = false;
+ boolean payerEmail = false;
+
+ // At most check the first 4 elements since there are only 4 different valid delegation
+ // types.
+ final int cappedArraySize =
+ Math.min(supportedDelegationsNames.length, /*MAX_DELEGATION_SIZE =*/4);
+ for (int i = 0; i < cappedArraySize; i++) {
+ if (supportedDelegationsNames[i] == null) {
+ Log.e(TAG,
+ "null is an invalid delegation value. Only [\"shippingAddress\", "
+ + "\"payerName\", \"payerPhone\", \"payerEmail\"] values "
+ + "are possible.");
+ } else if (supportedDelegationsNames[i].equals("shippingAddress")) {
+ shippingAddress = true;
+ } else if (supportedDelegationsNames[i].equals("payerName")) {
+ payerName = true;
+ } else if (supportedDelegationsNames[i].equals("payerPhone")) {
+ payerPhone = true;
+ } else if (supportedDelegationsNames[i].equals("payerEmail")) {
+ payerEmail = true;
+ } else {
+ Log.e(TAG,
+ "\"%s\" is an invalid delegation value. Only [\"shippingAddress\", "
+ + "\"payerName\", \"payerPhone\", \"payerEmail\"] values are "
+ + "possible.",
+ supportedDelegationsNames[i]);
+ }
+ }
+ return new SupportedDelegations(shippingAddress, payerName, payerPhone, payerEmail);
+ }
+}
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/OWNERS b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/OWNERS
new file mode 100644
index 00000000000..d6811b543c1
--- /dev/null
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/OWNERS
@@ -0,0 +1,8 @@
+# TEAM: payments-dev@chromium.org
+# COMPONENT: UI>Browser>Payments
+# OS: Android
+
+file://components/payments/OWNERS
+
+per-file *TypeConverter*.*=set noparent
+per-file *TypeConverter*.*=file://ipc/SECURITY_OWNERS \ No newline at end of file
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelper.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelper.java
index b035c031668..247375dd5f3 100644
--- a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelper.java
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelper.java
@@ -96,6 +96,22 @@ public class WebPaymentIntentHelper {
}
/**
+ * Get stringified payment details from a payment intent.
+ *
+ * @param data The payment intent data.
+ * @return The stringified payment details, if any.
+ */
+ @Nullable
+ public static String getPaymentIntentDetails(Intent data) {
+ String details = data.getExtras().getString(EXTRA_RESPONSE_DETAILS);
+ if (details == null) {
+ // try to get deprecated details rather than early returning.
+ details = data.getExtras().getString(EXTRA_DEPRECATED_RESPONSE_INSTRUMENT_DETAILS);
+ }
+ return details;
+ }
+
+ /**
* Parse the Payment Intent response.
* @param resultCode Result code of the requested intent.
* @param data The intent response data.
@@ -125,11 +141,7 @@ public class WebPaymentIntentHelper {
return;
}
- String details = data.getExtras().getString(EXTRA_RESPONSE_DETAILS);
- if (details == null) {
- // try to get deprecated details rather than early returning.
- details = data.getExtras().getString(EXTRA_DEPRECATED_RESPONSE_INSTRUMENT_DETAILS);
- }
+ String details = getPaymentIntentDetails(data);
if (TextUtils.isEmpty(details)) {
errorCallback.onPaymentError(ErrorStrings.MISSING_DETAILS_FROM_PAYMENT_APP);
return;
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelperTypeConverter.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelperTypeConverter.java
new file mode 100644
index 00000000000..b862a1d63c7
--- /dev/null
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelperTypeConverter.java
@@ -0,0 +1,195 @@
+// Copyright 2020 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.
+
+package org.chromium.components.payments.intent;
+
+import android.os.Bundle;
+import android.text.TextUtils;
+
+import androidx.annotation.Nullable;
+
+import org.chromium.base.CollectionUtil;
+import org.chromium.payments.mojom.AddressErrors;
+import org.chromium.payments.mojom.PaymentCurrencyAmount;
+import org.chromium.payments.mojom.PaymentDetailsModifier;
+import org.chromium.payments.mojom.PaymentItem;
+import org.chromium.payments.mojom.PaymentMethodData;
+import org.chromium.payments.mojom.PaymentOptions;
+import org.chromium.payments.mojom.PaymentRequestDetailsUpdate;
+import org.chromium.payments.mojom.PaymentShippingOption;
+import org.chromium.payments.mojom.PaymentShippingType;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class defines the utility functions that convert the payment info types in
+ * org.chromium.payments.mojom to their counterparts in WebPaymentIntentHelperType.
+ */
+public final class WebPaymentIntentHelperTypeConverter {
+ @Nullable
+ public static WebPaymentIntentHelperType.PaymentCurrencyAmount fromMojoPaymentCurrencyAmount(
+ @Nullable PaymentCurrencyAmount currencyAmount) {
+ if (currencyAmount == null) return null;
+ return new WebPaymentIntentHelperType.PaymentCurrencyAmount(
+ /*currency=*/currencyAmount.currency, /*value=*/currencyAmount.value);
+ }
+
+ @Nullable
+ public static WebPaymentIntentHelperType.PaymentItem fromMojoPaymentItem(
+ @Nullable PaymentItem item) {
+ if (item == null) return null;
+ return new WebPaymentIntentHelperType.PaymentItem(
+ fromMojoPaymentCurrencyAmount(item.amount));
+ }
+
+ @Nullable
+ public static WebPaymentIntentHelperType.PaymentDetailsModifier fromMojoPaymentDetailsModifier(
+ @Nullable PaymentDetailsModifier detailsModifier) {
+ if (detailsModifier == null) return null;
+ return new WebPaymentIntentHelperType.PaymentDetailsModifier(
+ fromMojoPaymentItem(detailsModifier.total),
+ fromMojoPaymentMethodData(detailsModifier.methodData));
+ }
+
+ @Nullable
+ public static WebPaymentIntentHelperType.PaymentMethodData fromMojoPaymentMethodData(
+ @Nullable PaymentMethodData methodData) {
+ if (methodData == null) return null;
+ return new WebPaymentIntentHelperType.PaymentMethodData(
+ /*supportedMethod=*/methodData.supportedMethod,
+ /*stringifiedData=*/methodData.stringifiedData);
+ }
+
+ @Nullable
+ public static Map<String, WebPaymentIntentHelperType.PaymentMethodData>
+ fromMojoPaymentMethodDataMap(@Nullable Map<String, PaymentMethodData> methodDataMap) {
+ if (methodDataMap == null) return null;
+ Map<String, WebPaymentIntentHelperType.PaymentMethodData> compatibleMethodDataMap =
+ new HashMap<>();
+ CollectionUtil.forEach(methodDataMap,
+ entry
+ -> compatibleMethodDataMap.put(entry.getKey(),
+ WebPaymentIntentHelperTypeConverter.fromMojoPaymentMethodData(
+ entry.getValue())));
+ return compatibleMethodDataMap;
+ }
+
+ @Nullable
+ public static Map<String, WebPaymentIntentHelperType.PaymentDetailsModifier>
+ fromMojoPaymentDetailsModifierMap(@Nullable Map<String, PaymentDetailsModifier> modifiers) {
+ if (modifiers == null) return null;
+ Map<String, WebPaymentIntentHelperType.PaymentDetailsModifier> compatibleModifiers =
+ new HashMap<>();
+ CollectionUtil.forEach(modifiers,
+ entry
+ -> compatibleModifiers.put(entry.getKey(),
+ WebPaymentIntentHelperTypeConverter.fromMojoPaymentDetailsModifier(
+ entry.getValue())));
+ return compatibleModifiers;
+ }
+
+ @Nullable
+ public static List<WebPaymentIntentHelperType.PaymentItem> fromMojoPaymentItems(
+ @Nullable List<PaymentItem> paymentItems) {
+ if (paymentItems == null) return null;
+ List<WebPaymentIntentHelperType.PaymentItem> compatiblePaymentItems = new ArrayList<>();
+ CollectionUtil.forEach(paymentItems,
+ element
+ -> compatiblePaymentItems.add(
+ WebPaymentIntentHelperTypeConverter.fromMojoPaymentItem(element)));
+ return compatiblePaymentItems;
+ }
+
+ @Nullable
+ public static WebPaymentIntentHelperType.PaymentShippingOption fromMojoPaymentShippingOption(
+ @Nullable PaymentShippingOption shippingOption) {
+ if (shippingOption == null) return null;
+ return new WebPaymentIntentHelperType.PaymentShippingOption(shippingOption.id,
+ shippingOption.label, fromMojoPaymentCurrencyAmount(shippingOption.amount),
+ shippingOption.selected);
+ }
+
+ @Nullable
+ public static List<WebPaymentIntentHelperType.PaymentShippingOption> fromMojoShippingOptionList(
+ @Nullable List<PaymentShippingOption> shippingOptions) {
+ if (shippingOptions == null) return null;
+ List<WebPaymentIntentHelperType.PaymentShippingOption> shippingOptionList =
+ new ArrayList<>();
+ CollectionUtil.forEach(shippingOptions,
+ element
+ -> shippingOptionList.add(
+ WebPaymentIntentHelperTypeConverter.fromMojoPaymentShippingOption(
+ element)));
+ return shippingOptionList;
+ }
+
+ @Nullable
+ public static WebPaymentIntentHelperType.PaymentOptions fromMojoPaymentOptions(
+ @Nullable PaymentOptions paymentOptions) {
+ if (paymentOptions == null) return null;
+ String shippingType = null;
+ if (paymentOptions.requestShipping) {
+ switch (paymentOptions.shippingType) {
+ case PaymentShippingType.SHIPPING:
+ shippingType = "shipping";
+ break;
+ case PaymentShippingType.DELIVERY:
+ shippingType = "delivery";
+ break;
+ case PaymentShippingType.PICKUP:
+ shippingType = "pickup";
+ break;
+ }
+ }
+ return new WebPaymentIntentHelperType.PaymentOptions(paymentOptions.requestPayerName,
+ paymentOptions.requestPayerEmail, paymentOptions.requestPayerPhone,
+ paymentOptions.requestShipping, shippingType);
+ }
+
+ @Nullable
+ private static Bundle fromMojoShippingAddressErrors(@Nullable AddressErrors addressErrors) {
+ if (addressErrors == null) return null;
+ Bundle bundle = new Bundle();
+ putIfNonEmpty("addressLine", addressErrors.addressLine, bundle);
+ putIfNonEmpty("city", addressErrors.city, bundle);
+ putIfNonEmpty("countryCode", addressErrors.country, bundle);
+ putIfNonEmpty("dependentLocality", addressErrors.dependentLocality, bundle);
+ putIfNonEmpty("organization", addressErrors.organization, bundle);
+ putIfNonEmpty("phone", addressErrors.phone, bundle);
+ putIfNonEmpty("postalCode", addressErrors.postalCode, bundle);
+ putIfNonEmpty("recipient", addressErrors.recipient, bundle);
+ putIfNonEmpty("region", addressErrors.region, bundle);
+ putIfNonEmpty("sortingCode", addressErrors.sortingCode, bundle);
+ return bundle;
+ }
+
+ private static void putIfNonEmpty(String key, String value, Bundle bundle) {
+ if (!TextUtils.isEmpty(value)) bundle.putString(key, value);
+ }
+
+ /**
+ * Converts PaymentRequestDetailsUpdate from mojo to
+ * WebPaymentIntentHelperType.PaymentRequestDetailsUpdate.
+ * @param update The mojo PaymentRequestDetailsUpdate to be converted.
+ * @return The converted update.
+ */
+ @Nullable
+ public static WebPaymentIntentHelperType.PaymentRequestDetailsUpdate
+ fromMojoPaymentRequestDetailsUpdate(@Nullable PaymentRequestDetailsUpdate update) {
+ if (update == null) return null;
+ return new WebPaymentIntentHelperType.PaymentRequestDetailsUpdate(
+ fromMojoPaymentCurrencyAmount(update.total),
+ // Arrays.asList does not accept null.
+ update.shippingOptions == null
+ ? null
+ : fromMojoShippingOptionList(Arrays.asList(update.shippingOptions)),
+ // update.modifiers is intentionally redacted.
+ update.error, update.stringifiedPaymentMethodErrors,
+ fromMojoShippingAddressErrors(update.shippingAddressErrors));
+ }
+}
diff --git a/chromium/components/payments/content/android/payment_feature_list.cc b/chromium/components/payments/content/android/payment_feature_list.cc
index c21c16bac3b..3a105c078ab 100644
--- a/chromium/components/payments/content/android/payment_feature_list.cc
+++ b/chromium/components/payments/content/android/payment_feature_list.cc
@@ -24,7 +24,9 @@ const base::Feature* kFeaturesExposedToJava[] = {
&::features::kWebPayments,
&::features::kWebPaymentsMinimalUI,
&features::kAlwaysAllowJustInTimePaymentApp,
+ &features::kAppStoreBilling,
&features::kAppStoreBillingDebug,
+ &features::kEnforceFullDelegation,
&features::kPaymentRequestSkipToGPay,
&features::kPaymentRequestSkipToGPayIfNoCard,
&features::kReturnGooglePayInBasicCard,
diff --git a/chromium/components/payments/content/android/payments_java_resources.gni b/chromium/components/payments/content/android/payments_java_resources.gni
new file mode 100644
index 00000000000..47a780d59c4
--- /dev/null
+++ b/chromium/components/payments/content/android/payments_java_resources.gni
@@ -0,0 +1,21 @@
+# Copyright 2020 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.
+
+payments_java_resources = [
+ "//components/payments/content/android/java/res/layout/payment_handler_content.xml",
+ "//components/payments/content/android/java/res/layout/payment_minimal_ui_content.xml",
+ "//components/payments/content/android/java/res/layout/payment_minimal_ui_toolbar.xml",
+ "//components/payments/content/android/java/res/layout/payment_option_edit_icon.xml",
+ "//components/payments/content/android/java/res/layout/payment_request.xml",
+ "//components/payments/content/android/java/res/layout/payment_request_bottom_bar.xml",
+ "//components/payments/content/android/java/res/layout/payment_request_dropdown_item.xml",
+ "//components/payments/content/android/java/res/layout/payment_request_editor.xml",
+ "//components/payments/content/android/java/res/layout/payment_request_editor_dropdown.xml",
+ "//components/payments/content/android/java/res/layout/payment_request_editor_label.xml",
+ "//components/payments/content/android/java/res/layout/payment_request_header.xml",
+ "//components/payments/content/android/java/res/layout/payment_request_spinny.xml",
+ "//components/payments/content/android/java/res/layout/payments_request_editor_textview.xml",
+ "//components/payments/content/android/java/res/values-sw600dp/dimens.xml",
+ "//components/payments/content/android/java/res/values/dimens.xml",
+]
diff --git a/chromium/components/payments/content/android_app_communication.cc b/chromium/components/payments/content/android_app_communication.cc
new file mode 100644
index 00000000000..212ccba2510
--- /dev/null
+++ b/chromium/components/payments/content/android_app_communication.cc
@@ -0,0 +1,48 @@
+// Copyright 2020 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/content/android_app_communication.h"
+
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace payments {
+namespace {
+
+const char kAndroidAppCommunicationKeyName[] =
+ "payment_android_app_communication";
+
+} // namespace
+
+// static
+base::WeakPtr<AndroidAppCommunication>
+AndroidAppCommunication::GetForBrowserContext(
+ content::BrowserContext* context) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DCHECK(context);
+
+ base::SupportsUserData::Data* data =
+ context->GetUserData(kAndroidAppCommunicationKeyName);
+ if (!data) {
+ auto communication = AndroidAppCommunication::Create(context);
+ data = communication.get();
+ context->SetUserData(kAndroidAppCommunicationKeyName,
+ std::move(communication));
+ }
+ return static_cast<AndroidAppCommunication*>(data)
+ ->weak_ptr_factory_.GetWeakPtr();
+}
+
+AndroidAppCommunication::~AndroidAppCommunication() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+}
+
+AndroidAppCommunication::AndroidAppCommunication(
+ content::BrowserContext* context)
+ : context_(context) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DCHECK(context_);
+}
+
+} // namespace payments
diff --git a/chromium/components/payments/content/android_app_communication.h b/chromium/components/payments/content/android_app_communication.h
new file mode 100644
index 00000000000..d4707c860bd
--- /dev/null
+++ b/chromium/components/payments/content/android_app_communication.h
@@ -0,0 +1,114 @@
+// Copyright 2020 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_ANDROID_APP_COMMUNICATION_H_
+#define COMPONENTS_PAYMENTS_CONTENT_ANDROID_APP_COMMUNICATION_H_
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "base/supports_user_data.h"
+#include "components/payments/core/android_app_description.h"
+
+class GURL;
+
+namespace content {
+class BrowserContext;
+} // namespace content
+
+namespace payments {
+
+// Invokes Android payment apps. This object is owned by BrowserContext, so it
+// should only be accessed on UI thread, where BrowserContext lives.
+class AndroidAppCommunication : public base::SupportsUserData::Data {
+ public:
+ using GetAppDescriptionsCallback = base::OnceCallback<void(
+ const base::Optional<std::string>& error_message,
+ std::vector<std::unique_ptr<AndroidAppDescription>> app_descriptions)>;
+
+ using IsReadyToPayCallback =
+ base::OnceCallback<void(const base::Optional<std::string>& error_message,
+ bool is_ready_to_pay)>;
+
+ using InvokePaymentAppCallback =
+ base::OnceCallback<void(const base::Optional<std::string>& error_message,
+ bool is_activity_result_ok,
+ const std::string& payment_method_identifier,
+ const std::string& stringified_details)>;
+
+ // Returns a weak pointer to the instance of AndroidAppCommunication that is
+ // owned by the given |context|, which should not be null.
+ static base::WeakPtr<AndroidAppCommunication> GetForBrowserContext(
+ content::BrowserContext* context);
+
+ ~AndroidAppCommunication() override;
+
+ // Disallow copy and assign.
+ AndroidAppCommunication(const AndroidAppCommunication& other) = delete;
+ AndroidAppCommunication& operator=(const AndroidAppCommunication& other) =
+ delete;
+
+ // Looks up installed Android apps that support making payments. If running in
+ // TWA mode, the |twa_package_name| parameter is the name of the Android
+ // package of the TWA that invoked Chrome, or an empty string otherwise.
+ virtual void GetAppDescriptions(const std::string& twa_package_name,
+ GetAppDescriptionsCallback callback) = 0;
+
+ // Queries the IS_READY_TO_PAY service to check whether the payment app can
+ // perform payments.
+ virtual void IsReadyToPay(const std::string& package_name,
+ const std::string& service_name,
+ const std::map<std::string, std::set<std::string>>&
+ stringified_method_data,
+ const GURL& top_level_origin,
+ const GURL& payment_request_origin,
+ const std::string& payment_request_id,
+ IsReadyToPayCallback callback) = 0;
+
+ // Invokes the PAY activity to initiate the payment flow.
+ virtual void InvokePaymentApp(
+ const std::string& package_name,
+ const std::string& activity_name,
+ const std::map<std::string, std::set<std::string>>&
+ stringified_method_data,
+ const GURL& top_level_origin,
+ const GURL& payment_request_origin,
+ const std::string& payment_request_id,
+ InvokePaymentAppCallback callback) = 0;
+
+ // Allows usage of a test browser context.
+ virtual void SetForTesting() = 0;
+
+ // Simulates having this payment app.
+ virtual void SetAppForTesting(const std::string& package_name,
+ const std::string& method,
+ const std::string& response) = 0;
+
+ protected:
+ explicit AndroidAppCommunication(content::BrowserContext* context);
+
+ content::BrowserContext* context() { return context_; }
+
+ private:
+ // Defined in platform-specific implementation files. See:
+ // components/payments/content/android_app_communication_chromeos.cc
+ // components/payments/content/android_app_communication_stub.cc
+ static std::unique_ptr<AndroidAppCommunication> Create(
+ content::BrowserContext* context);
+
+ // Owns this object, so always valid.
+ content::BrowserContext* context_;
+
+ base::WeakPtrFactory<AndroidAppCommunication> weak_ptr_factory_{this};
+};
+
+} // namespace payments
+
+#endif // COMPONENTS_PAYMENTS_CONTENT_ANDROID_APP_COMMUNICATION_H_
diff --git a/chromium/components/payments/content/android_app_communication_chrome_os.cc b/chromium/components/payments/content/android_app_communication_chrome_os.cc
new file mode 100644
index 00000000000..1012d35b2ed
--- /dev/null
+++ b/chromium/components/payments/content/android_app_communication_chrome_os.cc
@@ -0,0 +1,345 @@
+// Copyright 2020 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/content/android_app_communication.h"
+
+#include <utility>
+
+#include "components/arc/mojom/payment_app.mojom.h"
+#include "components/arc/pay/arc_payment_app_bridge.h"
+#include "components/payments/core/android_app_description.h"
+#include "components/payments/core/chrome_os_error_strings.h"
+#include "components/payments/core/method_strings.h"
+#include "components/payments/core/native_error_strings.h"
+#include "content/public/browser/browser_thread.h"
+#include "url/gurl.h"
+
+namespace payments {
+namespace {
+
+static constexpr char kEmptyDictionaryJson[] = "{}";
+
+void OnIsImplemented(
+ const std::string& twa_package_name,
+ AndroidAppCommunication::GetAppDescriptionsCallback callback,
+ arc::mojom::IsPaymentImplementedResultPtr response) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DCHECK(!twa_package_name.empty());
+
+ if (response.is_null()) {
+ std::move(callback).Run(errors::kEmptyResponse, /*app_descriptions=*/{});
+ return;
+ }
+
+ if (response->is_error()) {
+ std::move(callback).Run(response->get_error(), /*app_descriptions=*/{});
+ return;
+ }
+
+ if (!response->is_valid()) {
+ std::move(callback).Run(errors::kInvalidResponse, /*app_descriptions=*/{});
+ return;
+ }
+
+ if (response->get_valid()->activity_names.empty()) {
+ // If a TWA does not implement PAY intent in any of its activities, then
+ // |activity_names| is empty, which is not an error.
+ std::move(callback).Run(/*error_message=*/base::nullopt,
+ /*app_descriptions=*/{});
+ return;
+ }
+
+ if (response->get_valid()->activity_names.size() != 1U) {
+ std::move(callback).Run(errors::kMoreThanOneActivity,
+ /*app_descriptions=*/{});
+ return;
+ }
+
+ auto activity = std::make_unique<AndroidActivityDescription>();
+ activity->name = response->get_valid()->activity_names.front();
+
+ // The only available payment method identifier in a Chrome OS TWA at this
+ // time.
+ activity->default_payment_method = methods::kGooglePlayBilling;
+
+ auto app = std::make_unique<AndroidAppDescription>();
+ app->package = twa_package_name;
+ app->activities.emplace_back(std::move(activity));
+ app->service_names = response->get_valid()->service_names;
+
+ std::vector<std::unique_ptr<AndroidAppDescription>> app_descriptions;
+ app_descriptions.emplace_back(std::move(app));
+
+ std::move(callback).Run(/*error_message=*/base::nullopt,
+ std::move(app_descriptions));
+}
+
+void OnIsReadyToPay(AndroidAppCommunication::IsReadyToPayCallback callback,
+ arc::mojom::IsReadyToPayResultPtr response) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ if (response.is_null()) {
+ std::move(callback).Run(errors::kEmptyResponse, /*is_ready_to_pay=*/false);
+ return;
+ }
+
+ if (response->is_error()) {
+ std::move(callback).Run(response->get_error(), /*is_ready_to_pay=*/false);
+ return;
+ }
+
+ if (!response->is_response()) {
+ std::move(callback).Run(errors::kInvalidResponse,
+ /*is_ready_to_pay=*/false);
+ return;
+ }
+
+ std::move(callback).Run(/*error_message=*/base::nullopt,
+ response->get_response());
+}
+
+void OnPaymentAppResponse(
+ AndroidAppCommunication::InvokePaymentAppCallback callback,
+ arc::mojom::InvokePaymentAppResultPtr response) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ if (response.is_null()) {
+ std::move(callback).Run(errors::kEmptyResponse,
+ /*is_activity_result_ok=*/false,
+ /*payment_method_identifier=*/"",
+ /*stringified_details=*/kEmptyDictionaryJson);
+ return;
+ }
+
+ if (response->is_error()) {
+ std::move(callback).Run(response->get_error(),
+ /*is_activity_result_ok=*/false,
+ /*payment_method_identifier=*/"",
+ /*stringified_details=*/kEmptyDictionaryJson);
+ return;
+ }
+
+ if (!response->is_valid()) {
+ std::move(callback).Run(errors::kInvalidResponse,
+ /*is_activity_result_ok=*/false,
+ /*payment_method_identifier=*/"",
+ /*stringified_details=*/kEmptyDictionaryJson);
+ return;
+ }
+
+ // Chrome OS TWA currently supports only methods::kGooglePlayBilling payment
+ // method identifier.
+ std::move(callback).Run(
+ /*error_message=*/base::nullopt,
+ response->get_valid()->is_activity_result_ok,
+ /*payment_method_identifier=*/methods::kGooglePlayBilling,
+ response->get_valid()->stringified_details);
+}
+
+arc::mojom::PaymentParametersPtr CreatePaymentParameters(
+ const std::string& package_name,
+ const std::string& activity_or_service_name,
+ const std::map<std::string, std::set<std::string>>& stringified_method_data,
+ const GURL& top_level_origin,
+ const GURL& payment_request_origin,
+ const std::string& payment_request_id,
+ base::Optional<std::string>* error_message) {
+ // Chrome OS TWA supports only kGooglePlayBilling payment method identifier
+ // at this time.
+ auto supported_method_iterator =
+ stringified_method_data.find(methods::kGooglePlayBilling);
+ if (supported_method_iterator == stringified_method_data.end())
+ return nullptr;
+
+ // Chrome OS TWA supports only one set of payment method specific data.
+ if (supported_method_iterator->second.size() > 1) {
+ *error_message = errors::kMoreThanOneMethodData;
+ return nullptr;
+ }
+
+ auto parameters = arc::mojom::PaymentParameters::New();
+ parameters->stringified_method_data =
+ supported_method_iterator->second.empty()
+ ? kEmptyDictionaryJson
+ : *supported_method_iterator->second.begin();
+ parameters->package_name = package_name;
+ parameters->activity_or_service_name = activity_or_service_name;
+ parameters->top_level_origin = top_level_origin.spec();
+ parameters->payment_request_origin = payment_request_origin.spec();
+ parameters->payment_request_id = payment_request_id;
+
+ return parameters;
+}
+
+std::vector<std::unique_ptr<AndroidAppDescription>> CreateAppForTesting(
+ const std::string& package_name,
+ const std::string& method_name) {
+ auto activity = std::make_unique<AndroidActivityDescription>();
+ activity->name = package_name + ".Activity";
+ activity->default_payment_method = method_name;
+ auto app = std::make_unique<AndroidAppDescription>();
+ app->package = package_name;
+ app->activities.emplace_back(std::move(activity));
+ std::vector<std::unique_ptr<AndroidAppDescription>> app_descriptions;
+ app_descriptions.emplace_back(std::move(app));
+ return app_descriptions;
+}
+
+// Invokes the TWA Android app in Android subsystem on Chrome OS.
+class AndroidAppCommunicationChromeOS : public AndroidAppCommunication {
+ public:
+ explicit AndroidAppCommunicationChromeOS(content::BrowserContext* context)
+ : AndroidAppCommunication(context),
+ get_app_service_(base::BindRepeating(
+ &arc::ArcPaymentAppBridge::GetForBrowserContext)) {}
+
+ ~AndroidAppCommunicationChromeOS() override = default;
+
+ // Disallow copy and assign.
+ AndroidAppCommunicationChromeOS(
+ const AndroidAppCommunicationChromeOS& other) = delete;
+ AndroidAppCommunicationChromeOS& operator=(
+ const AndroidAppCommunicationChromeOS& other) = delete;
+
+ // AndroidAppCommunication implementation:
+ void GetAppDescriptions(const std::string& twa_package_name,
+ GetAppDescriptionsCallback callback) override {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ if (twa_package_name.empty()) {
+ // Chrome OS supports Android app payment only through a TWA. An empty
+ // |twa_package_name| indicates that Chrome was not launched from a TWA,
+ // so there're no payment apps available.
+ std::move(callback).Run(/*error_message=*/base::nullopt,
+ /*app_descriptions=*/{});
+ return;
+ }
+
+ if (!package_name_for_testing_.empty()) {
+ std::move(callback).Run(
+ /*error_message=*/base::nullopt,
+ CreateAppForTesting(package_name_for_testing_, method_for_testing_));
+ return;
+ }
+
+ auto* payment_app_service = get_app_service_.Run(context());
+ if (!payment_app_service) {
+ std::move(callback).Run(errors::kUnableToInvokeAndroidPaymentApps,
+ /*app_descriptions=*/{});
+ return;
+ }
+
+ payment_app_service->IsPaymentImplemented(
+ twa_package_name, base::BindOnce(&OnIsImplemented, twa_package_name,
+ std::move(callback)));
+ }
+
+ // AndroidAppCommunication implementation.
+ void IsReadyToPay(const std::string& package_name,
+ const std::string& service_name,
+ const std::map<std::string, std::set<std::string>>&
+ stringified_method_data,
+ const GURL& top_level_origin,
+ const GURL& payment_request_origin,
+ const std::string& payment_request_id,
+ IsReadyToPayCallback callback) override {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ auto* payment_app_service = get_app_service_.Run(context());
+ if (!payment_app_service) {
+ std::move(callback).Run(errors::kUnableToInvokeAndroidPaymentApps,
+ /*is_ready_to_pay=*/false);
+ return;
+ }
+
+ base::Optional<std::string> error_message;
+ auto parameters = CreatePaymentParameters(
+ package_name, service_name, stringified_method_data, top_level_origin,
+ payment_request_origin, payment_request_id, &error_message);
+ if (!parameters) {
+ std::move(callback).Run(error_message, /*is_ready_to_pay=*/false);
+ return;
+ }
+
+ payment_app_service->IsReadyToPay(
+ std::move(parameters),
+ base::BindOnce(&OnIsReadyToPay, std::move(callback)));
+ }
+
+ // AndroidAppCommunication implementation.
+ void InvokePaymentApp(const std::string& package_name,
+ const std::string& activity_name,
+ const std::map<std::string, std::set<std::string>>&
+ stringified_method_data,
+ const GURL& top_level_origin,
+ const GURL& payment_request_origin,
+ const std::string& payment_request_id,
+ InvokePaymentAppCallback callback) override {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ base::Optional<std::string> error_message;
+ if (package_name_for_testing_ == package_name) {
+ std::move(callback).Run(error_message,
+ /*is_activity_result_ok=*/true,
+ method_for_testing_, response_for_testing_);
+ return;
+ }
+
+ auto* payment_app_service = get_app_service_.Run(context());
+ if (!payment_app_service) {
+ std::move(callback).Run(errors::kUnableToInvokeAndroidPaymentApps,
+ /*is_activity_result_ok=*/false,
+ /*payment_method_identifier=*/"",
+ /*stringified_details=*/kEmptyDictionaryJson);
+ return;
+ }
+
+ auto parameters = CreatePaymentParameters(
+ package_name, activity_name, stringified_method_data, top_level_origin,
+ payment_request_origin, payment_request_id, &error_message);
+ if (!parameters) {
+ std::move(callback).Run(error_message,
+ /*is_activity_result_ok=*/false,
+ /*payment_method_identifier=*/"",
+ /*stringified_details=*/kEmptyDictionaryJson);
+ return;
+ }
+
+ payment_app_service->InvokePaymentApp(
+ std::move(parameters),
+ base::BindOnce(&OnPaymentAppResponse, std::move(callback)));
+ }
+
+ // AndroidAppCommunication implementation.
+ void SetForTesting() override {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ get_app_service_ = base::BindRepeating(
+ &arc::ArcPaymentAppBridge::GetForBrowserContextForTesting);
+ }
+
+ // AndroidAppCommunication implementation.
+ void SetAppForTesting(const std::string& package_name,
+ const std::string& method,
+ const std::string& response) override {
+ package_name_for_testing_ = package_name;
+ method_for_testing_ = method;
+ response_for_testing_ = response;
+ }
+
+ private:
+ std::string package_name_for_testing_;
+ std::string method_for_testing_;
+ std::string response_for_testing_;
+
+ base::RepeatingCallback<arc::ArcPaymentAppBridge*(content::BrowserContext*)>
+ get_app_service_;
+};
+
+} // namespace
+
+// Declared in cross-platform header file. See:
+// //components/payments/content/android_app_communication.h
+// static
+std::unique_ptr<AndroidAppCommunication> AndroidAppCommunication::Create(
+ content::BrowserContext* context) {
+ return std::make_unique<AndroidAppCommunicationChromeOS>(context);
+}
+
+} // namespace payments
diff --git a/chromium/components/payments/content/android_app_communication_stub.cc b/chromium/components/payments/content/android_app_communication_stub.cc
new file mode 100644
index 00000000000..b03ab067705
--- /dev/null
+++ b/chromium/components/payments/content/android_app_communication_stub.cc
@@ -0,0 +1,77 @@
+// Copyright 2020 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/content/android_app_communication.h"
+
+#include <utility>
+
+#include "base/callback.h"
+#include "base/optional.h"
+#include "components/payments/core/native_error_strings.h"
+
+namespace payments {
+namespace {
+
+class AndroidAppCommunicationStub : public AndroidAppCommunication {
+ public:
+ explicit AndroidAppCommunicationStub(content::BrowserContext* context)
+ : AndroidAppCommunication(context) {}
+
+ ~AndroidAppCommunicationStub() override = default;
+
+ // AndroidAppCommunication implementation.
+ void GetAppDescriptions(const std::string& twa_package_name,
+ GetAppDescriptionsCallback callback) override {
+ std::move(callback).Run(/*error_message=*/base::nullopt,
+ /*app_descriptions=*/{});
+ }
+
+ // AndroidAppCommunication implementation.
+ void IsReadyToPay(const std::string& package_name,
+ const std::string& service_name,
+ const std::map<std::string, std::set<std::string>>&
+ stringified_method_data,
+ const GURL& top_level_origin,
+ const GURL& payment_request_origin,
+ const std::string& payment_request_id,
+ IsReadyToPayCallback callback) override {
+ std::move(callback).Run(errors::kUnableToInvokeAndroidPaymentApps,
+ /*is_ready_to_pay=*/false);
+ }
+
+ // AndroidAppCommunication implementation.
+ void InvokePaymentApp(const std::string& package_name,
+ const std::string& activity_name,
+ const std::map<std::string, std::set<std::string>>&
+ stringified_method_data,
+ const GURL& top_level_origin,
+ const GURL& payment_request_origin,
+ const std::string& payment_request_id,
+ InvokePaymentAppCallback callback) override {
+ std::move(callback).Run(errors::kUnableToInvokeAndroidPaymentApps,
+ /*is_activity_result_ok=*/false,
+ /*payment_method_identifier=*/"",
+ /*stringified_details=*/"{}");
+ }
+
+ // AndroidAppCommunication implementation.
+ void SetForTesting() override {}
+
+ // AndroidAppCommunication implementation.
+ void SetAppForTesting(const std::string& package_name,
+ const std::string& method,
+ const std::string& response) override {}
+};
+
+} // namespace
+
+// Declared in cross-platform header file. See:
+// components/payments/content/android_app_communication.h
+// static
+std::unique_ptr<AndroidAppCommunication> AndroidAppCommunication::Create(
+ content::BrowserContext* context) {
+ return std::make_unique<AndroidAppCommunicationStub>(context);
+}
+
+} // namespace payments
diff --git a/chromium/components/payments/content/android_app_communication_test_support.h b/chromium/components/payments/content/android_app_communication_test_support.h
new file mode 100644
index 00000000000..22617d8960b
--- /dev/null
+++ b/chromium/components/payments/content/android_app_communication_test_support.h
@@ -0,0 +1,102 @@
+// Copyright 2020 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_ANDROID_APP_COMMUNICATION_TEST_SUPPORT_H_
+#define COMPONENTS_PAYMENTS_CONTENT_ANDROID_APP_COMMUNICATION_TEST_SUPPORT_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "components/payments/core/android_app_description.h"
+
+namespace content {
+class BrowserContext;
+} // namespace content
+
+namespace payments {
+
+// Cross-platform test support for Android payment app communication. On Chrome
+// OS, this connects to the Android subsystem.
+//
+// The test expectations are platform-specific. For example, on Chrome OS,
+// expectations are setup in the mock Mojo IPC service.
+class AndroidAppCommunicationTestSupport {
+ public:
+ // The object that initializes the ability to invoke Android apps in tests.
+ // For example, on Chrome OS, this object creates a mock Mojo IPC connection
+ // for the Android subsystem. This is meant to be placed on the stack, so it
+ // can perform clean up in its destructor.
+ class ScopedInitialization {
+ public:
+ ScopedInitialization() = default;
+ virtual ~ScopedInitialization() = default;
+
+ ScopedInitialization(const ScopedInitialization& other) = delete;
+ ScopedInitialization& operator=(const ScopedInitialization& other) = delete;
+ };
+
+ // Defined in platform-specific files.
+ static std::unique_ptr<AndroidAppCommunicationTestSupport> Create();
+
+ virtual ~AndroidAppCommunicationTestSupport() = default;
+
+ // Disallow copy and assign.
+ AndroidAppCommunicationTestSupport(
+ const AndroidAppCommunicationTestSupport& other) = delete;
+ AndroidAppCommunicationTestSupport& operator=(
+ const AndroidAppCommunicationTestSupport& other) = delete;
+
+ // Whether this platform supports Android apps. Used in tests to determine the
+ // expected outcome when attempting to query and invoke Android payment apps.
+ virtual bool AreAndroidAppsSupportedOnThisPlatform() const = 0;
+
+ // Creates the object that initializes the ability to invoke Android apps
+ // in tests. Places this on the stack, so it can perform clean up in its
+ // destructor. It's also useful to have a test case that does not invoke this
+ // method, so the code path that handles inability to invoke Android apps is
+ // tested.
+ virtual std::unique_ptr<ScopedInitialization>
+ CreateScopedInitialization() = 0;
+
+ // Sets up the expectation that the test case will not query the list of
+ // Android payment apps. This can happen, for example, when there is no
+ // ScopedInitialization object in scope.
+ virtual void ExpectNoListOfPaymentAppsQuery() = 0;
+
+ // Sets up the expectation that the test case will not query an
+ // IS_READY_TO_PAY service.
+ virtual void ExpectNoIsReadyToPayQuery() = 0;
+
+ // Sets up the expectation that the test case will not invoke a PAY activity.
+ virtual void ExpectNoPaymentAppInvoke() = 0;
+
+ // Sets up the expectation that the test case will query the list of Android
+ // payment apps. When that happens, the given list of |apps| will be used for
+ // the response.
+ virtual void ExpectQueryListOfPaymentAppsAndRespond(
+ std::vector<std::unique_ptr<AndroidAppDescription>> apps) = 0;
+
+ // Sets up the expectation that the test case will query an IS_READY_TO_PAY
+ // service. When that happens, the service will reply with the given
+ // |is_ready_to_pay| answer.
+ virtual void ExpectQueryIsReadyToPayAndRespond(bool is_ready_to_pay) = 0;
+
+ // Sets up the expectation that the test case will invoke a PAY activity. When
+ // that happens, the activity will reply with the given parameters.
+ virtual void ExpectInvokePaymentAppAndRespond(
+ bool is_activity_result_ok,
+ const std::string& payment_method_identifier,
+ const std::string& stringified_details) = 0;
+
+ // Returns the browser context to use.
+ virtual content::BrowserContext* context() = 0;
+
+ protected:
+ AndroidAppCommunicationTestSupport() = default;
+};
+
+} // namespace payments
+
+#endif // COMPONENTS_PAYMENTS_CONTENT_ANDROID_APP_COMMUNICATION_TEST_SUPPORT_H_
diff --git a/chromium/components/payments/content/android_app_communication_test_support_chrome_os.cc b/chromium/components/payments/content/android_app_communication_test_support_chrome_os.cc
new file mode 100644
index 00000000000..5cae13e86db
--- /dev/null
+++ b/chromium/components/payments/content/android_app_communication_test_support_chrome_os.cc
@@ -0,0 +1,160 @@
+// Copyright 2020 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/content/android_app_communication_test_support.h"
+
+#include <utility>
+
+#include "components/arc/mojom/payment_app.mojom.h"
+#include "components/arc/pay/arc_payment_app_bridge.h"
+#include "components/arc/test/arc_payment_app_bridge_test_support.h"
+#include "components/payments/core/method_strings.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace payments {
+namespace {
+
+class ScopedInitializationChromeOS
+ : public AndroidAppCommunicationTestSupport::ScopedInitialization {
+ public:
+ ScopedInitializationChromeOS(arc::ArcServiceManager* manager,
+ arc::mojom::PaymentAppInstance* instance)
+ : scoped_set_instance_(manager, instance) {}
+ ~ScopedInitializationChromeOS() override = default;
+
+ ScopedInitializationChromeOS(const ScopedInitializationChromeOS& other) =
+ delete;
+ ScopedInitializationChromeOS& operator=(
+ const ScopedInitializationChromeOS& other) = delete;
+
+ private:
+ arc::ArcPaymentAppBridgeTestSupport::ScopedSetInstance scoped_set_instance_;
+};
+
+class AndroidAppCommunicationTestSupportChromeOS
+ : public AndroidAppCommunicationTestSupport {
+ public:
+ AndroidAppCommunicationTestSupportChromeOS() = default;
+ ~AndroidAppCommunicationTestSupportChromeOS() override = default;
+
+ AndroidAppCommunicationTestSupportChromeOS(
+ const AndroidAppCommunicationTestSupportChromeOS& other) = delete;
+ AndroidAppCommunicationTestSupportChromeOS& operator=(
+ const AndroidAppCommunicationTestSupportChromeOS& other) = delete;
+
+ bool AreAndroidAppsSupportedOnThisPlatform() const override { return true; }
+
+ std::unique_ptr<ScopedInitialization> CreateScopedInitialization() override {
+ return std::make_unique<ScopedInitializationChromeOS>(support_.manager(),
+ support_.instance());
+ }
+
+ void ExpectNoListOfPaymentAppsQuery() override {
+ EXPECT_CALL(*support_.instance(),
+ IsPaymentImplemented(testing::_, testing::_))
+ .Times(0);
+ }
+
+ void ExpectNoIsReadyToPayQuery() override {
+ EXPECT_CALL(*support_.instance(), IsReadyToPay(testing::_, testing::_))
+ .Times(0);
+ }
+
+ void ExpectNoPaymentAppInvoke() override {
+ EXPECT_CALL(*support_.instance(), InvokePaymentApp(testing::_, testing::_))
+ .Times(0);
+ }
+
+ void ExpectQueryListOfPaymentAppsAndRespond(
+ std::vector<std::unique_ptr<AndroidAppDescription>> apps) override {
+ // Move |apps| into a member variable, so it's still alive by the time the
+ // RespondToGetAppDescriptions() method is executed at some point in the
+ // future.
+ apps_ = std::move(apps);
+ EXPECT_CALL(*support_.instance(),
+ IsPaymentImplemented(testing::_, testing::_))
+ .WillOnce(testing::Invoke(
+ [&](const std::string& package_name,
+ arc::ArcPaymentAppBridge::IsPaymentImplementedCallback
+ callback) {
+ RespondToGetAppDescriptions(package_name, std::move(callback));
+ }));
+ }
+
+ void ExpectQueryIsReadyToPayAndRespond(bool is_ready_to_pay) override {
+ EXPECT_CALL(*support_.instance(), IsReadyToPay(testing::_, testing::_))
+ .WillOnce(testing::Invoke(
+ [is_ready_to_pay](
+ arc::mojom::PaymentParametersPtr parameters,
+ arc::ArcPaymentAppBridge::IsReadyToPayCallback callback) {
+ std::move(callback).Run(
+ arc::mojom::IsReadyToPayResult::NewResponse(is_ready_to_pay));
+ }));
+ }
+
+ void ExpectInvokePaymentAppAndRespond(
+ bool is_activity_result_ok,
+ const std::string& payment_method_identifier,
+ const std::string& stringified_details) override {
+ EXPECT_CALL(*support_.instance(), InvokePaymentApp(testing::_, testing::_))
+ .WillOnce(testing::Invoke(
+ [is_activity_result_ok, stringified_details](
+ arc::mojom::PaymentParametersPtr parameters,
+ arc::ArcPaymentAppBridge::InvokePaymentAppCallback callback) {
+ // Chrome OS supports only kGooglePlayBilling payment method
+ // identifier at this time, so the |payment_method_identifier|
+ // parameter is ignored here.
+ auto valid = arc::mojom::InvokePaymentAppValidResult::New();
+ valid->is_activity_result_ok = is_activity_result_ok;
+ valid->stringified_details = stringified_details;
+ std::move(callback).Run(
+ arc::mojom::InvokePaymentAppResult::NewValid(
+ std::move(valid)));
+ }));
+ }
+
+ content::BrowserContext* context() override { return support_.context(); }
+
+ private:
+ void RespondToGetAppDescriptions(
+ const std::string& package_name,
+ arc::ArcPaymentAppBridge::IsPaymentImplementedCallback callback) {
+ auto valid = arc::mojom::IsPaymentImplementedValidResult::New();
+ for (const auto& app : apps_) {
+ if (app->package == package_name) {
+ for (const auto& activity : app->activities) {
+ // Chrome OS supports only kGooglePlayBilling method at this time.
+ if (activity->default_payment_method == methods::kGooglePlayBilling) {
+ valid->activity_names.push_back(activity->name);
+ }
+ }
+
+ valid->service_names = app->service_names;
+
+ // Chrome OS supports only one payment app in Android subsystem at this
+ // time, i.e., the TWA that invoked Chrome.
+ break;
+ }
+ }
+
+ std::move(callback).Run(
+ arc::mojom::IsPaymentImplementedResult::NewValid(std::move(valid)));
+ }
+
+ arc::ArcPaymentAppBridgeTestSupport support_;
+ std::vector<std::unique_ptr<AndroidAppDescription>> apps_;
+};
+
+} // namespace
+
+// Declared in cross-platform file
+// //components/payments/content/android_app_communication_test_support.h
+// static
+std::unique_ptr<AndroidAppCommunicationTestSupport>
+AndroidAppCommunicationTestSupport::Create() {
+ return std::make_unique<AndroidAppCommunicationTestSupportChromeOS>();
+}
+
+} // namespace payments
diff --git a/chromium/components/payments/content/android_app_communication_test_support_stub.cc b/chromium/components/payments/content/android_app_communication_test_support_stub.cc
new file mode 100644
index 00000000000..93df4287e60
--- /dev/null
+++ b/chromium/components/payments/content/android_app_communication_test_support_stub.cc
@@ -0,0 +1,65 @@
+// Copyright 2020 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/content/android_app_communication_test_support.h"
+
+#include <utility>
+
+#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_browser_context.h"
+
+namespace payments {
+namespace {
+
+class AndroidAppCommunicationTestSupportStub
+ : public AndroidAppCommunicationTestSupport {
+ public:
+ AndroidAppCommunicationTestSupportStub() = default;
+ ~AndroidAppCommunicationTestSupportStub() override = default;
+
+ AndroidAppCommunicationTestSupportStub(
+ const AndroidAppCommunicationTestSupportStub& other) = delete;
+ AndroidAppCommunicationTestSupportStub& operator=(
+ const AndroidAppCommunicationTestSupportStub& other) = delete;
+
+ bool AreAndroidAppsSupportedOnThisPlatform() const override { return false; }
+
+ std::unique_ptr<ScopedInitialization> CreateScopedInitialization() override {
+ return std::make_unique<ScopedInitialization>();
+ }
+
+ void ExpectNoListOfPaymentAppsQuery() override {}
+
+ void ExpectNoIsReadyToPayQuery() override {}
+
+ void ExpectNoPaymentAppInvoke() override {}
+
+ void ExpectQueryListOfPaymentAppsAndRespond(
+ std::vector<std::unique_ptr<AndroidAppDescription>> apps) override {}
+
+ void ExpectQueryIsReadyToPayAndRespond(bool is_ready_to_pay) override {}
+
+ void ExpectInvokePaymentAppAndRespond(
+ bool is_activity_result_ok,
+ const std::string& payment_method_identifier,
+ const std::string& stringified_details) override {}
+
+ content::BrowserContext* context() override { return &context_; }
+
+ private:
+ content::BrowserTaskEnvironment environment_;
+ content::TestBrowserContext context_;
+};
+
+} // namespace
+
+// Declared in cross-platform file
+// //components/payments/content/android_app_communication_test_support.h
+// static
+std::unique_ptr<AndroidAppCommunicationTestSupport>
+AndroidAppCommunicationTestSupport::Create() {
+ return std::make_unique<AndroidAppCommunicationTestSupportStub>();
+}
+
+} // namespace payments
diff --git a/chromium/components/payments/content/android_app_communication_unittest.cc b/chromium/components/payments/content/android_app_communication_unittest.cc
new file mode 100644
index 00000000000..552e46b3234
--- /dev/null
+++ b/chromium/components/payments/content/android_app_communication_unittest.cc
@@ -0,0 +1,639 @@
+// Copyright 2020 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/content/android_app_communication.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/optional.h"
+#include "components/payments/content/android_app_communication_test_support.h"
+#include "components/payments/core/android_app_description.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace payments {
+namespace {
+
+std::vector<std::unique_ptr<AndroidAppDescription>> createApp(
+ const std::vector<std::string>& activity_names,
+ const std::string& default_payment_method,
+ const std::vector<std::string>& service_names) {
+ auto app = std::make_unique<AndroidAppDescription>();
+
+ for (const auto& activity_name : activity_names) {
+ auto activity = std::make_unique<AndroidActivityDescription>();
+ activity->name = activity_name;
+ activity->default_payment_method = default_payment_method;
+ app->activities.emplace_back(std::move(activity));
+ }
+
+ app->package = "com.example.app";
+ app->service_names = service_names;
+
+ std::vector<std::unique_ptr<AndroidAppDescription>> apps;
+ apps.emplace_back(std::move(app));
+
+ return apps;
+}
+
+class AndroidAppCommunicationTest : public testing::Test {
+ public:
+ AndroidAppCommunicationTest()
+ : support_(AndroidAppCommunicationTestSupport::Create()) {}
+ ~AndroidAppCommunicationTest() override = default;
+
+ AndroidAppCommunicationTest(const AndroidAppCommunicationTest& other) =
+ delete;
+ AndroidAppCommunicationTest& operator=(
+ const AndroidAppCommunicationTest& other) = delete;
+
+ void OnGetAppDescriptionsResponse(
+ const base::Optional<std::string>& error,
+ std::vector<std::unique_ptr<AndroidAppDescription>> apps) {
+ error_ = error;
+ apps_ = std::move(apps);
+ }
+
+ void OnIsReadyToPayResponse(const base::Optional<std::string>& error,
+ bool is_ready_to_pay) {
+ error_ = error;
+ is_ready_to_pay_ = is_ready_to_pay;
+ }
+
+ void OnPaymentAppResponse(const base::Optional<std::string>& error,
+ bool is_activity_result_ok,
+ const std::string& payment_method_identifier,
+ const std::string& stringified_details) {
+ error_ = error;
+ is_activity_result_ok_ = is_activity_result_ok;
+ payment_method_identifier_ = payment_method_identifier;
+ stringified_details_ = stringified_details;
+ }
+
+ std::unique_ptr<AndroidAppCommunicationTestSupport> support_;
+ base::Optional<std::string> error_;
+ std::vector<std::unique_ptr<AndroidAppDescription>> apps_;
+ bool is_ready_to_pay_ = false;
+ bool is_activity_result_ok_ = false;
+ std::string payment_method_identifier_;
+ std::string stringified_details_;
+};
+
+TEST_F(AndroidAppCommunicationTest, OneInstancePerBrowserContext) {
+ auto communication_one =
+ AndroidAppCommunication::GetForBrowserContext(support_->context());
+ auto communication_two =
+ AndroidAppCommunication::GetForBrowserContext(support_->context());
+ EXPECT_EQ(communication_one.get(), communication_two.get());
+}
+
+TEST_F(AndroidAppCommunicationTest, NoArcForGetAppDescriptions) {
+ // Intentionally do not set an instance.
+
+ support_->ExpectNoListOfPaymentAppsQuery();
+
+ auto communication =
+ AndroidAppCommunication::GetForBrowserContext(support_->context());
+ communication->SetForTesting();
+ communication->GetAppDescriptions(
+ "com.example.app",
+ base::BindOnce(&AndroidAppCommunicationTest::OnGetAppDescriptionsResponse,
+ base::Unretained(this)));
+
+ if (support_->AreAndroidAppsSupportedOnThisPlatform()) {
+ ASSERT_TRUE(error_.has_value());
+ EXPECT_EQ("Unable to invoke Android apps.", error_.value());
+ } else {
+ EXPECT_FALSE(error_.has_value());
+ }
+
+ EXPECT_TRUE(apps_.empty());
+}
+
+TEST_F(AndroidAppCommunicationTest, NoAppDescriptions) {
+ auto scoped_initialization = support_->CreateScopedInitialization();
+
+ support_->ExpectQueryListOfPaymentAppsAndRespond({});
+
+ auto communication =
+ AndroidAppCommunication::GetForBrowserContext(support_->context());
+ communication->SetForTesting();
+ communication->GetAppDescriptions(
+ "com.example.app",
+ base::BindOnce(&AndroidAppCommunicationTest::OnGetAppDescriptionsResponse,
+ base::Unretained(this)));
+
+ EXPECT_FALSE(error_.has_value());
+ EXPECT_TRUE(apps_.empty());
+}
+
+TEST_F(AndroidAppCommunicationTest, TwoActivitiesInPackage) {
+ auto scoped_initialization = support_->CreateScopedInitialization();
+
+ support_->ExpectQueryListOfPaymentAppsAndRespond(
+ createApp({"com.example.app.ActivityOne", "com.example.app.ActivityTwo"},
+ "https://play.google.com/billing", {}));
+
+ auto communication =
+ AndroidAppCommunication::GetForBrowserContext(support_->context());
+ communication->SetForTesting();
+ communication->GetAppDescriptions(
+ "com.example.app",
+ base::BindOnce(&AndroidAppCommunicationTest::OnGetAppDescriptionsResponse,
+ base::Unretained(this)));
+
+ if (support_->AreAndroidAppsSupportedOnThisPlatform()) {
+ ASSERT_TRUE(error_.has_value());
+ EXPECT_EQ(
+ "Found more than one PAY activity in the Trusted Web Activity, but at "
+ "most one activity is supported.",
+ error_.value());
+ } else {
+ EXPECT_FALSE(error_.has_value());
+ }
+ EXPECT_TRUE(apps_.empty());
+}
+
+TEST_F(AndroidAppCommunicationTest, TwoServicesInPackage) {
+ auto scoped_initialization = support_->CreateScopedInitialization();
+
+ support_->ExpectQueryListOfPaymentAppsAndRespond(
+ createApp({"com.example.app.Activity"}, "https://play.google.com/billing",
+ {"com.example.app.ServiceOne", "com.example.app.ServiceTwo"}));
+
+ auto communication =
+ AndroidAppCommunication::GetForBrowserContext(support_->context());
+ communication->SetForTesting();
+ communication->GetAppDescriptions(
+ "com.example.app",
+ base::BindOnce(&AndroidAppCommunicationTest::OnGetAppDescriptionsResponse,
+ base::Unretained(this)));
+
+ EXPECT_FALSE(error_.has_value());
+ if (support_->AreAndroidAppsSupportedOnThisPlatform()) {
+ ASSERT_EQ(1u, apps_.size());
+ ASSERT_NE(nullptr, apps_.front().get());
+ EXPECT_EQ("com.example.app", apps_.front()->package);
+
+ // The logic for checking for multiple services is cross-platform in
+ // android_payment_app_factory.cc, so the platform-specific implementations
+ // of android_app_communication.h do not check for this error condition.
+ std::vector<std::string> expected_service_names = {
+ "com.example.app.ServiceOne", "com.example.app.ServiceTwo"};
+ EXPECT_EQ(expected_service_names, apps_.front()->service_names);
+
+ ASSERT_EQ(1u, apps_.front()->activities.size());
+ ASSERT_NE(nullptr, apps_.front()->activities.front().get());
+ EXPECT_EQ("com.example.app.Activity",
+ apps_.front()->activities.front()->name);
+ EXPECT_EQ("https://play.google.com/billing",
+ apps_.front()->activities.front()->default_payment_method);
+ } else {
+ EXPECT_TRUE(apps_.empty());
+ }
+}
+
+TEST_F(AndroidAppCommunicationTest, ActivityAndService) {
+ auto scoped_initialization = support_->CreateScopedInitialization();
+
+ support_->ExpectQueryListOfPaymentAppsAndRespond(
+ createApp({"com.example.app.Activity"}, "https://play.google.com/billing",
+ {"com.example.app.Service"}));
+
+ auto communication =
+ AndroidAppCommunication::GetForBrowserContext(support_->context());
+ communication->SetForTesting();
+ communication->GetAppDescriptions(
+ "com.example.app",
+ base::BindOnce(&AndroidAppCommunicationTest::OnGetAppDescriptionsResponse,
+ base::Unretained(this)));
+
+ EXPECT_FALSE(error_.has_value());
+
+ if (support_->AreAndroidAppsSupportedOnThisPlatform()) {
+ ASSERT_EQ(1u, apps_.size());
+ ASSERT_NE(nullptr, apps_.front().get());
+ EXPECT_EQ("com.example.app", apps_.front()->package);
+ EXPECT_EQ(std::vector<std::string>{"com.example.app.Service"},
+ apps_.front()->service_names);
+ ASSERT_EQ(1u, apps_.front()->activities.size());
+ ASSERT_NE(nullptr, apps_.front()->activities.front().get());
+ EXPECT_EQ("com.example.app.Activity",
+ apps_.front()->activities.front()->name);
+ EXPECT_EQ("https://play.google.com/billing",
+ apps_.front()->activities.front()->default_payment_method);
+ } else {
+ EXPECT_TRUE(apps_.empty());
+ }
+}
+
+TEST_F(AndroidAppCommunicationTest, OnlyActivity) {
+ auto scoped_initialization = support_->CreateScopedInitialization();
+
+ support_->ExpectQueryListOfPaymentAppsAndRespond(createApp(
+ {"com.example.app.Activity"}, "https://play.google.com/billing", {}));
+
+ auto communication =
+ AndroidAppCommunication::GetForBrowserContext(support_->context());
+ communication->SetForTesting();
+ communication->GetAppDescriptions(
+ "com.example.app",
+ base::BindOnce(&AndroidAppCommunicationTest::OnGetAppDescriptionsResponse,
+ base::Unretained(this)));
+
+ EXPECT_FALSE(error_.has_value());
+
+ if (support_->AreAndroidAppsSupportedOnThisPlatform()) {
+ ASSERT_EQ(1u, apps_.size());
+ ASSERT_NE(nullptr, apps_.front().get());
+ EXPECT_EQ("com.example.app", apps_.front()->package);
+ EXPECT_TRUE(apps_.front()->service_names.empty());
+ ASSERT_EQ(1u, apps_.front()->activities.size());
+ ASSERT_NE(nullptr, apps_.front()->activities.front().get());
+ EXPECT_EQ("com.example.app.Activity",
+ apps_.front()->activities.front()->name);
+ EXPECT_EQ("https://play.google.com/billing",
+ apps_.front()->activities.front()->default_payment_method);
+ } else {
+ EXPECT_TRUE(apps_.empty());
+ }
+}
+
+TEST_F(AndroidAppCommunicationTest, OutsideOfTwa) {
+ auto scoped_initialization = support_->CreateScopedInitialization();
+
+ support_->ExpectNoListOfPaymentAppsQuery();
+
+ auto communication =
+ AndroidAppCommunication::GetForBrowserContext(support_->context());
+ communication->SetForTesting();
+ communication->GetAppDescriptions(
+ /*twa_package_name=*/"", // Empty string means this is not TWA.
+ base::BindOnce(&AndroidAppCommunicationTest::OnGetAppDescriptionsResponse,
+ base::Unretained(this)));
+
+ EXPECT_FALSE(error_.has_value());
+ EXPECT_TRUE(apps_.empty());
+}
+
+TEST_F(AndroidAppCommunicationTest, NoArcForIsReadyToPay) {
+ // Intentionally do not set an instance.
+
+ support_->ExpectNoIsReadyToPayQuery();
+
+ auto communication =
+ AndroidAppCommunication::GetForBrowserContext(support_->context());
+ communication->SetForTesting();
+
+ std::map<std::string, std::set<std::string>> stringified_method_data;
+ stringified_method_data["https://play.google.com/billing"].insert("{}");
+ communication->IsReadyToPay(
+ "com.example.app", "com.example.app.Service", stringified_method_data,
+ GURL("https://top-level-origin.com"),
+ GURL("https://payment-request-origin.com"), "payment-request-id",
+ base::BindOnce(&AndroidAppCommunicationTest::OnIsReadyToPayResponse,
+ base::Unretained(this)));
+
+ ASSERT_TRUE(error_.has_value());
+ EXPECT_EQ("Unable to invoke Android apps.", error_.value());
+ EXPECT_FALSE(is_ready_to_pay_);
+}
+
+TEST_F(AndroidAppCommunicationTest, TwaIsReadyToPayOnlyWithPlayBilling) {
+ auto scoped_initialization = support_->CreateScopedInitialization();
+
+ support_->ExpectNoIsReadyToPayQuery();
+
+ auto communication =
+ AndroidAppCommunication::GetForBrowserContext(support_->context());
+ communication->SetForTesting();
+
+ std::map<std::string, std::set<std::string>> stringified_method_data;
+ stringified_method_data["https://example.com"].insert("{}");
+ communication->IsReadyToPay(
+ "com.example.app", "com.example.app.Service", stringified_method_data,
+ GURL("https://top-level-origin.com"),
+ GURL("https://payment-request-origin.com"), "payment-request-id",
+ base::BindOnce(&AndroidAppCommunicationTest::OnIsReadyToPayResponse,
+ base::Unretained(this)));
+
+ if (support_->AreAndroidAppsSupportedOnThisPlatform()) {
+ EXPECT_FALSE(error_.has_value());
+ } else {
+ ASSERT_TRUE(error_.has_value());
+ EXPECT_EQ("Unable to invoke Android apps.", error_.value());
+ }
+
+ EXPECT_FALSE(is_ready_to_pay_);
+}
+
+TEST_F(AndroidAppCommunicationTest, MoreThanOnePaymentMethodDataNotReadyToPay) {
+ auto scoped_initialization = support_->CreateScopedInitialization();
+
+ support_->ExpectNoIsReadyToPayQuery();
+
+ auto communication =
+ AndroidAppCommunication::GetForBrowserContext(support_->context());
+ communication->SetForTesting();
+
+ std::map<std::string, std::set<std::string>> stringified_method_data;
+ stringified_method_data["https://play.google.com/billing"].insert(
+ "{\"product_id\": \"1\"}");
+ stringified_method_data["https://play.google.com/billing"].insert(
+ "{\"product_id\": \"2\"}");
+ communication->IsReadyToPay(
+ "com.example.app", "com.example.app.Service", stringified_method_data,
+ GURL("https://top-level-origin.com"),
+ GURL("https://payment-request-origin.com"), "payment-request-id",
+ base::BindOnce(&AndroidAppCommunicationTest::OnIsReadyToPayResponse,
+ base::Unretained(this)));
+
+ ASSERT_TRUE(error_.has_value());
+
+ if (support_->AreAndroidAppsSupportedOnThisPlatform()) {
+ EXPECT_EQ("At most one payment method specific data is supported.",
+ error_.value());
+ } else {
+ EXPECT_EQ("Unable to invoke Android apps.", error_.value());
+ }
+
+ EXPECT_FALSE(is_ready_to_pay_);
+}
+
+TEST_F(AndroidAppCommunicationTest, EmptyMethodDataIsReadyToPay) {
+ auto scoped_initialization = support_->CreateScopedInitialization();
+
+ support_->ExpectQueryIsReadyToPayAndRespond(true);
+
+ auto communication =
+ AndroidAppCommunication::GetForBrowserContext(support_->context());
+ communication->SetForTesting();
+
+ std::map<std::string, std::set<std::string>> stringified_method_data;
+ stringified_method_data.insert(std::make_pair(
+ "https://play.google.com/billing", std::set<std::string>()));
+ communication->IsReadyToPay(
+ "com.example.app", "com.example.app.Service", stringified_method_data,
+ GURL("https://top-level-origin.com"),
+ GURL("https://payment-request-origin.com"), "payment-request-id",
+ base::BindOnce(&AndroidAppCommunicationTest::OnIsReadyToPayResponse,
+ base::Unretained(this)));
+
+ if (support_->AreAndroidAppsSupportedOnThisPlatform()) {
+ EXPECT_FALSE(error_.has_value());
+ EXPECT_TRUE(is_ready_to_pay_);
+ } else {
+ ASSERT_TRUE(error_.has_value());
+ EXPECT_EQ("Unable to invoke Android apps.", error_.value());
+ EXPECT_FALSE(is_ready_to_pay_);
+ }
+}
+
+TEST_F(AndroidAppCommunicationTest, NotReadyToPay) {
+ auto scoped_initialization = support_->CreateScopedInitialization();
+
+ support_->ExpectQueryIsReadyToPayAndRespond(false);
+
+ auto communication =
+ AndroidAppCommunication::GetForBrowserContext(support_->context());
+ communication->SetForTesting();
+
+ std::map<std::string, std::set<std::string>> stringified_method_data;
+ stringified_method_data["https://play.google.com/billing"].insert("{}");
+ communication->IsReadyToPay(
+ "com.example.app", "com.example.app.Service", stringified_method_data,
+ GURL("https://top-level-origin.com"),
+ GURL("https://payment-request-origin.com"), "payment-request-id",
+ base::BindOnce(&AndroidAppCommunicationTest::OnIsReadyToPayResponse,
+ base::Unretained(this)));
+
+ if (support_->AreAndroidAppsSupportedOnThisPlatform()) {
+ EXPECT_FALSE(error_.has_value());
+ } else {
+ ASSERT_TRUE(error_.has_value());
+ EXPECT_EQ("Unable to invoke Android apps.", error_.value());
+ }
+
+ EXPECT_FALSE(is_ready_to_pay_);
+}
+
+TEST_F(AndroidAppCommunicationTest, ReadyToPay) {
+ auto scoped_initialization = support_->CreateScopedInitialization();
+
+ support_->ExpectQueryIsReadyToPayAndRespond(true);
+
+ auto communication =
+ AndroidAppCommunication::GetForBrowserContext(support_->context());
+ communication->SetForTesting();
+
+ std::map<std::string, std::set<std::string>> stringified_method_data;
+ stringified_method_data["https://play.google.com/billing"].insert("{}");
+ communication->IsReadyToPay(
+ "com.example.app", "com.example.app.Service", stringified_method_data,
+ GURL("https://top-level-origin.com"),
+ GURL("https://payment-request-origin.com"), "payment-request-id",
+ base::BindOnce(&AndroidAppCommunicationTest::OnIsReadyToPayResponse,
+ base::Unretained(this)));
+
+ if (support_->AreAndroidAppsSupportedOnThisPlatform()) {
+ EXPECT_FALSE(error_.has_value());
+ EXPECT_TRUE(is_ready_to_pay_);
+ } else {
+ ASSERT_TRUE(error_.has_value());
+ EXPECT_EQ("Unable to invoke Android apps.", error_.value());
+ EXPECT_FALSE(is_ready_to_pay_);
+ }
+}
+
+TEST_F(AndroidAppCommunicationTest, NoArcForInvokePaymentApp) {
+ // Intentionally do not set an instance.
+
+ support_->ExpectNoPaymentAppInvoke();
+
+ auto communication =
+ AndroidAppCommunication::GetForBrowserContext(support_->context());
+ communication->SetForTesting();
+
+ std::map<std::string, std::set<std::string>> stringified_method_data;
+ stringified_method_data["https://play.google.com/billing"].insert("{}");
+ communication->InvokePaymentApp(
+ "com.example.app", "com.example.app.Activity", stringified_method_data,
+ GURL("https://top-level-origin.com"),
+ GURL("https://payment-request-origin.com"), "payment-request-id",
+ base::BindOnce(&AndroidAppCommunicationTest::OnPaymentAppResponse,
+ base::Unretained(this)));
+
+ EXPECT_EQ("Unable to invoke Android apps.", error_.value());
+ EXPECT_FALSE(is_activity_result_ok_);
+ EXPECT_TRUE(payment_method_identifier_.empty());
+ EXPECT_EQ("{}", stringified_details_);
+}
+
+TEST_F(AndroidAppCommunicationTest, TwaPaymentOnlyWithPlayBilling) {
+ auto scoped_initialization = support_->CreateScopedInitialization();
+
+ support_->ExpectNoPaymentAppInvoke();
+
+ auto communication =
+ AndroidAppCommunication::GetForBrowserContext(support_->context());
+ communication->SetForTesting();
+
+ std::map<std::string, std::set<std::string>> stringified_method_data;
+ stringified_method_data["https://example.com"].insert("{}");
+ communication->InvokePaymentApp(
+ "com.example.app", "com.example.app.Activity", stringified_method_data,
+ GURL("https://top-level-origin.com"),
+ GURL("https://payment-request-origin.com"), "payment-request-id",
+ base::BindOnce(&AndroidAppCommunicationTest::OnPaymentAppResponse,
+ base::Unretained(this)));
+
+ if (support_->AreAndroidAppsSupportedOnThisPlatform()) {
+ EXPECT_FALSE(error_.has_value());
+ EXPECT_FALSE(is_activity_result_ok_);
+ EXPECT_TRUE(payment_method_identifier_.empty());
+ EXPECT_EQ("{}", stringified_details_);
+ } else {
+ ASSERT_TRUE(error_.has_value());
+ EXPECT_EQ("Unable to invoke Android apps.", error_.value());
+ }
+}
+
+TEST_F(AndroidAppCommunicationTest, NoPaymentWithMoreThanOnePaymentMethodData) {
+ auto scoped_initialization = support_->CreateScopedInitialization();
+
+ support_->ExpectNoPaymentAppInvoke();
+
+ auto communication =
+ AndroidAppCommunication::GetForBrowserContext(support_->context());
+ communication->SetForTesting();
+
+ std::map<std::string, std::set<std::string>> stringified_method_data;
+ stringified_method_data["https://play.google.com/billing"].insert(
+ "{\"product_id\": \"1\"}");
+ stringified_method_data["https://play.google.com/billing"].insert(
+ "{\"product_id\": \"2\"}");
+ communication->InvokePaymentApp(
+ "com.example.app", "com.example.app.Activity", stringified_method_data,
+ GURL("https://top-level-origin.com"),
+ GURL("https://payment-request-origin.com"), "payment-request-id",
+ base::BindOnce(&AndroidAppCommunicationTest::OnPaymentAppResponse,
+ base::Unretained(this)));
+
+ EXPECT_FALSE(is_activity_result_ok_);
+ EXPECT_EQ("{}", stringified_details_);
+ EXPECT_TRUE(payment_method_identifier_.empty());
+ ASSERT_TRUE(error_.has_value());
+
+ if (support_->AreAndroidAppsSupportedOnThisPlatform()) {
+ EXPECT_EQ("At most one payment method specific data is supported.",
+ error_.value());
+ } else {
+ EXPECT_EQ("Unable to invoke Android apps.", error_.value());
+ }
+}
+
+TEST_F(AndroidAppCommunicationTest, PaymentWithEmptyMethodData) {
+ auto scoped_initialization = support_->CreateScopedInitialization();
+
+ support_->ExpectInvokePaymentAppAndRespond(
+ /*is_activity_result_ok=*/true,
+ /*payment_method_identifier=*/"https://play.google.com/billing",
+ /*stringified_details*/ "{\"status\": \"ok\"}");
+
+ auto communication =
+ AndroidAppCommunication::GetForBrowserContext(support_->context());
+ communication->SetForTesting();
+
+ std::map<std::string, std::set<std::string>> stringified_method_data;
+ stringified_method_data.insert(std::make_pair(
+ "https://play.google.com/billing", std::set<std::string>()));
+ communication->InvokePaymentApp(
+ "com.example.app", "com.example.app.Activity", stringified_method_data,
+ GURL("https://top-level-origin.com"),
+ GURL("https://payment-request-origin.com"), "payment-request-id",
+ base::BindOnce(&AndroidAppCommunicationTest::OnPaymentAppResponse,
+ base::Unretained(this)));
+
+ if (support_->AreAndroidAppsSupportedOnThisPlatform()) {
+ EXPECT_FALSE(error_.has_value());
+ EXPECT_TRUE(is_activity_result_ok_);
+ EXPECT_EQ("https://play.google.com/billing", payment_method_identifier_);
+ EXPECT_EQ("{\"status\": \"ok\"}", stringified_details_);
+ } else {
+ ASSERT_TRUE(error_.has_value());
+ EXPECT_EQ("Unable to invoke Android apps.", error_.value());
+ }
+}
+
+TEST_F(AndroidAppCommunicationTest, UserCancelInvokePaymentApp) {
+ auto scoped_initialization = support_->CreateScopedInitialization();
+
+ support_->ExpectInvokePaymentAppAndRespond(
+ /*is_activity_result_ok=*/false,
+ /*payment_method_identifier=*/"https://play.google.com/billing",
+ /*stringified_details*/ "{}");
+
+ auto communication =
+ AndroidAppCommunication::GetForBrowserContext(support_->context());
+ communication->SetForTesting();
+
+ std::map<std::string, std::set<std::string>> stringified_method_data;
+ stringified_method_data["https://play.google.com/billing"].insert("{}");
+ communication->InvokePaymentApp(
+ "com.example.app", "com.example.app.Activity", stringified_method_data,
+ GURL("https://top-level-origin.com"),
+ GURL("https://payment-request-origin.com"), "payment-request-id",
+ base::BindOnce(&AndroidAppCommunicationTest::OnPaymentAppResponse,
+ base::Unretained(this)));
+
+ if (support_->AreAndroidAppsSupportedOnThisPlatform()) {
+ EXPECT_FALSE(error_.has_value());
+ EXPECT_FALSE(is_activity_result_ok_);
+ EXPECT_EQ("https://play.google.com/billing", payment_method_identifier_);
+ EXPECT_EQ("{}", stringified_details_);
+ } else {
+ ASSERT_TRUE(error_.has_value());
+ EXPECT_EQ("Unable to invoke Android apps.", error_.value());
+ }
+}
+
+TEST_F(AndroidAppCommunicationTest, UserConfirmInvokePaymentApp) {
+ auto scoped_initialization = support_->CreateScopedInitialization();
+
+ support_->ExpectInvokePaymentAppAndRespond(
+ /*is_activity_result_ok=*/true,
+ /*payment_method_identifier=*/"https://play.google.com/billing",
+ /*stringified_details*/ "{\"status\": \"ok\"}");
+
+ auto communication =
+ AndroidAppCommunication::GetForBrowserContext(support_->context());
+ communication->SetForTesting();
+
+ std::map<std::string, std::set<std::string>> stringified_method_data;
+ stringified_method_data["https://play.google.com/billing"].insert("{}");
+ communication->InvokePaymentApp(
+ "com.example.app", "com.example.app.Activity", stringified_method_data,
+ GURL("https://top-level-origin.com"),
+ GURL("https://payment-request-origin.com"), "payment-request-id",
+ base::BindOnce(&AndroidAppCommunicationTest::OnPaymentAppResponse,
+ base::Unretained(this)));
+
+ if (support_->AreAndroidAppsSupportedOnThisPlatform()) {
+ EXPECT_FALSE(error_.has_value());
+ EXPECT_TRUE(is_activity_result_ok_);
+ EXPECT_EQ("https://play.google.com/billing", payment_method_identifier_);
+ EXPECT_EQ("{\"status\": \"ok\"}", stringified_details_);
+ } else {
+ ASSERT_TRUE(error_.has_value());
+ EXPECT_EQ("Unable to invoke Android apps.", error_.value());
+ }
+}
+
+} // namespace
+} // namespace payments
diff --git a/chromium/components/payments/content/android_payment_app.cc b/chromium/components/payments/content/android_payment_app.cc
new file mode 100644
index 00000000000..6871e68e00e
--- /dev/null
+++ b/chromium/components/payments/content/android_payment_app.cc
@@ -0,0 +1,178 @@
+// Copyright 2020 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/content/android_payment_app.h"
+
+#include <utility>
+
+#include "base/check.h"
+#include "components/payments/core/method_strings.h"
+#include "components/payments/core/native_error_strings.h"
+#include "components/payments/core/payer_data.h"
+
+namespace payments {
+
+AndroidPaymentApp::AndroidPaymentApp(
+ const std::set<std::string>& payment_method_names,
+ std::unique_ptr<std::map<std::string, std::set<std::string>>>
+ stringified_method_data,
+ const GURL& top_level_origin,
+ const GURL& payment_request_origin,
+ const std::string& payment_request_id,
+ std::unique_ptr<AndroidAppDescription> description,
+ base::WeakPtr<AndroidAppCommunication> communication)
+ : PaymentApp(/*icon_resource_id=*/0, PaymentApp::Type::NATIVE_MOBILE_APP),
+ stringified_method_data_(std::move(stringified_method_data)),
+ top_level_origin_(top_level_origin),
+ payment_request_origin_(payment_request_origin),
+ payment_request_id_(payment_request_id),
+ description_(std::move(description)),
+ communication_(communication) {
+ DCHECK(!payment_method_names.empty());
+ DCHECK_EQ(payment_method_names.size(), stringified_method_data_->size());
+ DCHECK_EQ(*payment_method_names.begin(),
+ stringified_method_data_->begin()->first);
+ DCHECK(description_);
+ DCHECK(!description_->package.empty());
+ DCHECK_EQ(1U, description_->activities.size());
+ DCHECK(!description_->activities.front()->name.empty());
+
+ app_method_names_ = payment_method_names;
+}
+
+AndroidPaymentApp::~AndroidPaymentApp() = default;
+
+void AndroidPaymentApp::InvokePaymentApp(Delegate* delegate) {
+ // Browser is closing, so no need to invoke a callback.
+ if (!communication_)
+ return;
+
+ communication_->InvokePaymentApp(
+ description_->package, description_->activities.front()->name,
+ *stringified_method_data_, top_level_origin_, payment_request_origin_,
+ payment_request_id_,
+ base::BindOnce(&AndroidPaymentApp::OnPaymentAppResponse,
+ weak_ptr_factory_.GetWeakPtr(), delegate));
+}
+
+bool AndroidPaymentApp::IsCompleteForPayment() const {
+ return true;
+}
+
+uint32_t AndroidPaymentApp::GetCompletenessScore() const {
+ return 0;
+}
+
+bool AndroidPaymentApp::CanPreselect() const {
+ return true;
+}
+
+base::string16 AndroidPaymentApp::GetMissingInfoLabel() const {
+ NOTREACHED();
+ return base::string16();
+}
+
+bool AndroidPaymentApp::HasEnrolledInstrument() const {
+ return true;
+}
+
+void AndroidPaymentApp::RecordUse() {
+ NOTIMPLEMENTED();
+}
+
+bool AndroidPaymentApp::NeedsInstallation() const {
+ return false;
+}
+
+std::string AndroidPaymentApp::GetId() const {
+ return description_->package;
+}
+
+base::string16 AndroidPaymentApp::GetLabel() const {
+ return base::string16();
+}
+
+base::string16 AndroidPaymentApp::GetSublabel() const {
+ return base::string16();
+}
+
+const SkBitmap* AndroidPaymentApp::icon_bitmap() const {
+ return nullptr;
+}
+
+bool AndroidPaymentApp::IsValidForModifier(
+ const std::string& method,
+ bool supported_networks_specified,
+ const std::set<std::string>& supported_networks) const {
+ bool is_valid = false;
+ IsValidForPaymentMethodIdentifier(method, &is_valid);
+ return is_valid;
+}
+
+base::WeakPtr<PaymentApp> AndroidPaymentApp::AsWeakPtr() {
+ return weak_ptr_factory_.GetWeakPtr();
+}
+
+bool AndroidPaymentApp::HandlesShippingAddress() const {
+ return false;
+}
+
+bool AndroidPaymentApp::HandlesPayerName() const {
+ return false;
+}
+
+bool AndroidPaymentApp::HandlesPayerEmail() const {
+ return false;
+}
+
+bool AndroidPaymentApp::HandlesPayerPhone() const {
+ return false;
+}
+
+bool AndroidPaymentApp::IsWaitingForPaymentDetailsUpdate() const {
+ return false;
+}
+
+void AndroidPaymentApp::UpdateWith(
+ mojom::PaymentRequestDetailsUpdatePtr details_update) {
+ // TODO(crbug.com/1022512): Support payment method, shipping address, and
+ // shipping option change events.
+}
+
+void AndroidPaymentApp::OnPaymentDetailsNotUpdated() {}
+
+bool AndroidPaymentApp::IsPreferred() const {
+ // This class used only on Chrome OS, where the only Android payment app
+ // available is the trusted web application (TWA) that launched this instance
+ // of Chrome with a TWA specific payment method, so this app should be
+ // preferred.
+#if !defined(OS_CHROMEOS)
+ NOTREACHED();
+#endif // OS_CHROMEOS
+ DCHECK_EQ(1U, GetAppMethodNames().size());
+ DCHECK_EQ(methods::kGooglePlayBilling, *GetAppMethodNames().begin());
+ return true;
+}
+
+void AndroidPaymentApp::OnPaymentAppResponse(
+ Delegate* delegate,
+ const base::Optional<std::string>& error_message,
+ bool is_activity_result_ok,
+ const std::string& payment_method_identifier,
+ const std::string& stringified_details) {
+ if (error_message.has_value()) {
+ delegate->OnInstrumentDetailsError(error_message.value());
+ return;
+ }
+
+ if (!is_activity_result_ok) {
+ delegate->OnInstrumentDetailsError(errors::kUserClosedPaymentApp);
+ return;
+ }
+
+ delegate->OnInstrumentDetailsReady(payment_method_identifier,
+ stringified_details, PayerData());
+}
+
+} // namespace payments
diff --git a/chromium/components/payments/content/android_payment_app.h b/chromium/components/payments/content/android_payment_app.h
new file mode 100644
index 00000000000..0e826e0edfa
--- /dev/null
+++ b/chromium/components/payments/content/android_payment_app.h
@@ -0,0 +1,97 @@
+// Copyright 2020 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_ANDROID_PAYMENT_APP_H_
+#define COMPONENTS_PAYMENTS_CONTENT_ANDROID_PAYMENT_APP_H_
+
+#include <memory>
+#include <set>
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "components/payments/content/android_app_communication.h"
+#include "components/payments/content/payment_app.h"
+#include "components/payments/core/android_app_description.h"
+#include "url/gurl.h"
+
+namespace payments {
+
+// A cross-platform way to invoke an Android payment app.
+class AndroidPaymentApp : public PaymentApp {
+ public:
+ // Creates an instance of AndroidPaymentApp.
+ //
+ // The |payment_method_names| is the set of payment method identifiers
+ // supported by this app, e.g., ["https://example1.com",
+ // "https://example2.com"]. This set should not be empty.
+ //
+ // The |stringified_method_data| is a mapping from payment method identifiers
+ // that this app can handle to the method-specific data provided by the
+ // merchant. The set of keys should match exactly the |payment_method_names|.
+ // It is the responsibility of the creator of AndroidPaymentApp to filter out
+ // the data from merchant that is not in |payment_method_names|.
+ AndroidPaymentApp(
+ const std::set<std::string>& payment_method_names,
+ std::unique_ptr<std::map<std::string, std::set<std::string>>>
+ stringified_method_data,
+ const GURL& top_level_origin,
+ const GURL& payment_request_origin,
+ const std::string& payment_request_id,
+ std::unique_ptr<AndroidAppDescription> description,
+ base::WeakPtr<AndroidAppCommunication> communication);
+ ~AndroidPaymentApp() override;
+
+ AndroidPaymentApp(const AndroidPaymentApp& other) = delete;
+ AndroidPaymentApp& operator=(const AndroidPaymentApp& other) = delete;
+
+ // PaymentApp implementation.
+ void InvokePaymentApp(Delegate* delegate) override;
+ bool IsCompleteForPayment() const override;
+ uint32_t GetCompletenessScore() const override;
+ bool CanPreselect() const override;
+ base::string16 GetMissingInfoLabel() const override;
+ bool HasEnrolledInstrument() const override;
+ void RecordUse() override;
+ bool NeedsInstallation() const override;
+ std::string GetId() const override;
+ base::string16 GetLabel() const override;
+ base::string16 GetSublabel() const override;
+ const SkBitmap* icon_bitmap() const override;
+ bool IsValidForModifier(
+ const std::string& method,
+ bool supported_networks_specified,
+ const std::set<std::string>& supported_networks) const override;
+ base::WeakPtr<PaymentApp> AsWeakPtr() override;
+ bool HandlesShippingAddress() const override;
+ bool HandlesPayerName() const override;
+ bool HandlesPayerEmail() const override;
+ bool HandlesPayerPhone() const override;
+ bool IsWaitingForPaymentDetailsUpdate() const override;
+ void UpdateWith(
+ mojom::PaymentRequestDetailsUpdatePtr details_update) override;
+ void OnPaymentDetailsNotUpdated() override;
+ bool IsPreferred() const override;
+
+ private:
+ void OnPaymentAppResponse(Delegate* delegate,
+ const base::Optional<std::string>& error_message,
+ bool is_activity_result_ok,
+ const std::string& payment_method_identifier,
+ const std::string& stringified_details);
+
+ const std::unique_ptr<std::map<std::string, std::set<std::string>>>
+ stringified_method_data_;
+ const GURL top_level_origin_;
+ const GURL payment_request_origin_;
+ const std::string payment_request_id_;
+ const std::unique_ptr<AndroidAppDescription> description_;
+ base::WeakPtr<AndroidAppCommunication> communication_;
+
+ base::WeakPtrFactory<AndroidPaymentApp> weak_ptr_factory_{this};
+};
+
+} // namespace payments
+
+#endif // COMPONENTS_PAYMENTS_CONTENT_ANDROID_PAYMENT_APP_H_
diff --git a/chromium/components/payments/content/android_payment_app_factory.cc b/chromium/components/payments/content/android_payment_app_factory.cc
new file mode 100644
index 00000000000..5611de2da5b
--- /dev/null
+++ b/chromium/components/payments/content/android_payment_app_factory.cc
@@ -0,0 +1,231 @@
+// Copyright 2020 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/content/android_payment_app_factory.h"
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/memory/weak_ptr.h"
+#include "base/stl_util.h"
+#include "base/supports_user_data.h"
+#include "components/payments/content/android_app_communication.h"
+#include "components/payments/content/android_payment_app.h"
+#include "components/payments/content/payment_request_spec.h"
+#include "components/payments/core/android_app_description.h"
+#include "components/payments/core/android_app_description_tools.h"
+#include "components/payments/core/method_strings.h"
+#include "components/payments/core/native_error_strings.h"
+#include "components/payments/core/payment_request_data_util.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_document_host_user_data.h"
+#include "content/public/browser/web_contents.h"
+
+namespace payments {
+namespace {
+
+class AppFinder : public base::SupportsUserData::Data {
+ public:
+ static base::WeakPtr<AppFinder> CreateAndSetOwnedBy(
+ base::SupportsUserData* owner) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DCHECK(owner);
+ auto owned = std::make_unique<AppFinder>(owner);
+ auto weak_ptr = owned->weak_ptr_factory_.GetWeakPtr();
+ const void* key = owned.get();
+ owner->SetUserData(key, std::move(owned));
+ return weak_ptr;
+ }
+
+ explicit AppFinder(base::SupportsUserData* owner) : owner_(owner) {}
+ ~AppFinder() override = default;
+
+ AppFinder(const AppFinder& other) = delete;
+ AppFinder& operator=(const AppFinder& other) = delete;
+
+ void FindApps(base::WeakPtr<AndroidAppCommunication> communication,
+ base::WeakPtr<PaymentAppFactory::Delegate> delegate) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DCHECK_EQ(nullptr, delegate_.get());
+ DCHECK_NE(nullptr, delegate.get());
+ DCHECK_EQ(0U, number_of_pending_is_ready_to_pay_queries_);
+ DCHECK_EQ(nullptr, communication_.get());
+ DCHECK_NE(nullptr, communication.get());
+ DCHECK(delegate->GetSpec());
+ DCHECK(delegate->GetSpec()->details().id.has_value());
+
+ delegate_ = delegate;
+ communication_ = communication;
+
+ std::string twa_package_name = delegate_->GetTwaPackageName();
+ std::set<std::string> twa_payment_method_names = {
+ methods::kGooglePlayBilling,
+ };
+ if (twa_package_name.empty() ||
+ base::STLSetIntersection<std::set<std::string>>(
+ delegate_->GetSpec()->payment_method_identifiers_set(),
+ twa_payment_method_names)
+ .empty()) {
+ OnDoneCreatingPaymentApps();
+ return;
+ }
+
+ communication_->GetAppDescriptions(
+ twa_package_name, base::BindOnce(&AppFinder::OnGetAppDescriptions,
+ weak_ptr_factory_.GetWeakPtr()));
+ }
+
+ private:
+ void OnGetAppDescriptions(
+ const base::Optional<std::string>& error_message,
+ std::vector<std::unique_ptr<AndroidAppDescription>> app_descriptions) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ // The browser could be shutting down.
+ if (!communication_ || !delegate_ || !delegate_->GetSpec())
+ return;
+
+ if (error_message.has_value()) {
+ delegate_->OnPaymentAppCreationError(error_message.value());
+ OnDoneCreatingPaymentApps();
+ return;
+ }
+
+ std::vector<std::unique_ptr<AndroidAppDescription>> single_activity_apps;
+ for (size_t i = 0; i < app_descriptions.size(); ++i) {
+ auto app = std::move(app_descriptions[i]);
+ if (app->service_names.size() > 1U) {
+ delegate_->OnPaymentAppCreationError(errors::kMoreThanOneService);
+ continue;
+ }
+
+ // Move each activity in the given |app| to its own AndroidAppDescription
+ // in |single_activity_apps|, so the code can treat each PAY intent as its
+ // own payment app. This allows Android apps to implement PAY intent in
+ // multiple activities with different names and icons for different use
+ // cases.
+ SplitPotentiallyMultipleActivities(std::move(app), &single_activity_apps);
+ }
+
+ number_of_pending_is_ready_to_pay_queries_ = single_activity_apps.size();
+ if (number_of_pending_is_ready_to_pay_queries_ == 0U) {
+ OnDoneCreatingPaymentApps();
+ return;
+ }
+
+ for (size_t i = 0; i < single_activity_apps.size(); ++i) {
+ std::unique_ptr<AndroidAppDescription> single_activity_app =
+ std::move(single_activity_apps[i]);
+
+ const std::string& default_method =
+ single_activity_app->activities.front()->default_payment_method;
+ DCHECK_EQ(methods::kGooglePlayBilling, default_method);
+
+ std::set<std::string> supported_payment_methods = {default_method};
+ std::set<std::string> payment_method_names =
+ base::STLSetIntersection<std::set<std::string>>(
+ delegate_->GetSpec()->payment_method_identifiers_set(),
+ supported_payment_methods);
+
+ std::unique_ptr<std::map<std::string, std::set<std::string>>>
+ stringified_method_data = data_util::FilterStringifiedMethodData(
+ delegate_->GetSpec()->stringified_method_data(),
+ supported_payment_methods);
+
+ // TODO(crbug.com/1022512): Download the web app manifest for
+ // |default_payment_method_name| to verify Android app signature.
+
+ // Skip querying IS_READY_TO_PAY service when Chrome is off-the-record or
+ // when the app does not implement the IS_READY_TO_PAY service.
+ if (delegate_->IsOffTheRecord() ||
+ single_activity_app->service_names.empty()) {
+ OnIsReadyToPay(std::move(single_activity_app), payment_method_names,
+ std::move(stringified_method_data),
+ /*error_message=*/base::nullopt,
+ /*is_ready_to_pay=*/true);
+ continue;
+ }
+
+ std::map<std::string, std::set<std::string>>
+ stringified_method_data_copy = *stringified_method_data;
+ communication_->IsReadyToPay(
+ single_activity_app->package,
+ single_activity_app->service_names.front(),
+ stringified_method_data_copy, delegate_->GetTopOrigin(),
+ delegate_->GetFrameOrigin(),
+ delegate_->GetSpec()->details().id.value(),
+ base::BindOnce(&AppFinder::OnIsReadyToPay,
+ weak_ptr_factory_.GetWeakPtr(),
+ std::move(single_activity_app), payment_method_names,
+ std::move(stringified_method_data)));
+ }
+ }
+
+ void OnIsReadyToPay(
+ std::unique_ptr<AndroidAppDescription> app_description,
+ const std::set<std::string>& payment_method_names,
+ std::unique_ptr<std::map<std::string, std::set<std::string>>>
+ stringified_method_data,
+ const base::Optional<std::string>& error_message,
+ bool is_ready_to_pay) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DCHECK_LT(0U, number_of_pending_is_ready_to_pay_queries_);
+
+ // The browser could be shutting down.
+ if (!communication_ || !delegate_ || !delegate_->GetSpec()) {
+ OnDoneCreatingPaymentApps();
+ return;
+ }
+
+ if (error_message.has_value()) {
+ delegate_->OnPaymentAppCreationError(error_message.value());
+ } else if (is_ready_to_pay) {
+ delegate_->OnPaymentAppCreated(std::make_unique<AndroidPaymentApp>(
+ payment_method_names, std::move(stringified_method_data),
+ delegate_->GetTopOrigin(), delegate_->GetFrameOrigin(),
+ delegate_->GetSpec()->details().id.value(),
+ std::move(app_description), communication_));
+ }
+
+ if (--number_of_pending_is_ready_to_pay_queries_ == 0)
+ OnDoneCreatingPaymentApps();
+ }
+
+ void OnDoneCreatingPaymentApps() {
+ if (delegate_)
+ delegate_->OnDoneCreatingPaymentApps();
+
+ owner_->RemoveUserData(this);
+ }
+
+ base::SupportsUserData* owner_;
+ base::WeakPtr<PaymentAppFactory::Delegate> delegate_;
+ size_t number_of_pending_is_ready_to_pay_queries_ = 0;
+ base::WeakPtr<AndroidAppCommunication> communication_;
+
+ base::WeakPtrFactory<AppFinder> weak_ptr_factory_{this};
+};
+
+} // namespace
+
+AndroidPaymentAppFactory::AndroidPaymentAppFactory(
+ base::WeakPtr<AndroidAppCommunication> communication)
+ : PaymentAppFactory(PaymentApp::Type::NATIVE_MOBILE_APP),
+ communication_(communication) {
+ DCHECK(communication_);
+}
+
+AndroidPaymentAppFactory::~AndroidPaymentAppFactory() = default;
+
+void AndroidPaymentAppFactory::Create(base::WeakPtr<Delegate> delegate) {
+ auto app_finder = AppFinder::CreateAndSetOwnedBy(delegate->GetWebContents());
+ app_finder->FindApps(communication_, delegate);
+}
+
+} // namespace payments
diff --git a/chromium/components/payments/content/android_payment_app_factory.h b/chromium/components/payments/content/android_payment_app_factory.h
new file mode 100644
index 00000000000..09b4df56ee7
--- /dev/null
+++ b/chromium/components/payments/content/android_payment_app_factory.h
@@ -0,0 +1,37 @@
+// Copyright 2020 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_ANDROID_PAYMENT_APP_FACTORY_H_
+#define COMPONENTS_PAYMENTS_CONTENT_ANDROID_PAYMENT_APP_FACTORY_H_
+
+#include "base/memory/weak_ptr.h"
+#include "components/payments/content/payment_app_factory.h"
+
+namespace payments {
+
+class AndroidAppCommunication;
+
+// Retrieves Android payment apps.
+class AndroidPaymentAppFactory : public PaymentAppFactory {
+ public:
+ // The given |communication| is used for communication with Android payment
+ // apps.
+ explicit AndroidPaymentAppFactory(
+ base::WeakPtr<AndroidAppCommunication> communication);
+ ~AndroidPaymentAppFactory() override;
+
+ AndroidPaymentAppFactory(const AndroidPaymentAppFactory& other) = delete;
+ AndroidPaymentAppFactory& operator=(const AndroidPaymentAppFactory& other) =
+ delete;
+
+ // PaymentAppFactory:
+ void Create(base::WeakPtr<Delegate> delegate) override;
+
+ private:
+ base::WeakPtr<AndroidAppCommunication> communication_;
+};
+
+} // namespace payments
+
+#endif // COMPONENTS_PAYMENTS_CONTENT_ANDROID_PAYMENT_APP_FACTORY_H_
diff --git a/chromium/components/payments/content/android_payment_app_factory_unittest.cc b/chromium/components/payments/content/android_payment_app_factory_unittest.cc
new file mode 100644
index 00000000000..4f53c6a5241
--- /dev/null
+++ b/chromium/components/payments/content/android_payment_app_factory_unittest.cc
@@ -0,0 +1,519 @@
+// Copyright 2020 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/content/android_payment_app_factory.h"
+
+#include <utility>
+
+#include "base/memory/weak_ptr.h"
+#include "base/stl_util.h"
+#include "components/autofill/core/browser/payments/internal_authenticator.h"
+#include "components/payments/content/android_app_communication.h"
+#include "components/payments/content/android_app_communication_test_support.h"
+#include "components/payments/content/payment_app_factory.h"
+#include "components/payments/content/payment_manifest_web_data_service.h"
+#include "components/payments/content/payment_request_spec.h"
+#include "components/payments/core/android_app_description.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/test_web_contents_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace content {
+class BrowserContext;
+} // namespace content
+
+namespace payments {
+namespace {
+
+// A mock delegate for payment app factories.
+class MockPaymentAppFactoryDelegate : public PaymentAppFactory::Delegate {
+ public:
+ explicit MockPaymentAppFactoryDelegate(content::BrowserContext* context)
+ : web_contents_(web_contents_factory_.CreateWebContents(context)),
+ top_origin_("https://top-origin.test"),
+ frame_origin_("https://frame-origin.test") {
+ SetRequestedPaymentMethod("https://play.google.com/billing");
+ }
+
+ ~MockPaymentAppFactoryDelegate() override = default;
+
+ void SetRequestedPaymentMethod(const std::string& method) {
+ auto details = mojom::PaymentDetails::New();
+ details->id = "id";
+
+ std::vector<mojom::PaymentMethodDataPtr> methods;
+ methods.emplace_back(mojom::PaymentMethodData::New());
+ methods.back()->supported_method = method;
+ methods.back()->stringified_data = "{}";
+
+ spec_ = std::make_unique<PaymentRequestSpec>(
+ mojom::PaymentOptions::New(), std::move(details), std::move(methods),
+ /*observer=*/nullptr, /*app_locale=*/"en-US");
+ }
+
+ void set_is_off_the_record() { is_off_the_record_ = true; }
+
+ // PaymentAppFactory::Delegate implementation:
+ content::WebContents* GetWebContents() override { return web_contents_; }
+ const GURL& GetTopOrigin() override { return top_origin_; }
+ const GURL& GetFrameOrigin() override { return frame_origin_; }
+ MOCK_METHOD0(GetFrameSecurityOrigin, const url::Origin&());
+ MOCK_CONST_METHOD0(GetInitiatorRenderFrameHost, content::RenderFrameHost*());
+ MOCK_CONST_METHOD0(GetMethodData,
+ const std::vector<mojom::PaymentMethodDataPtr>&());
+ MOCK_CONST_METHOD0(CreateInternalAuthenticator,
+ std::unique_ptr<autofill::InternalAuthenticator>());
+ MOCK_CONST_METHOD0(GetPaymentManifestWebDataService,
+ scoped_refptr<PaymentManifestWebDataService>());
+ MOCK_METHOD0(MayCrawlForInstallablePaymentApps, bool());
+ bool IsOffTheRecord() const override { return is_off_the_record_; }
+ PaymentRequestSpec* GetSpec() const override { return spec_.get(); }
+ MOCK_CONST_METHOD0(GetTwaPackageName, std::string());
+ MOCK_METHOD0(ShowProcessingSpinner, void());
+ MOCK_METHOD0(GetBillingProfiles,
+ const std::vector<autofill::AutofillProfile*>&());
+ MOCK_METHOD0(IsRequestedAutofillDataAvailable, bool());
+ MOCK_CONST_METHOD0(GetPaymentRequestDelegate,
+ ContentPaymentRequestDelegate*());
+ MOCK_METHOD1(OnPaymentAppCreated, void(std::unique_ptr<PaymentApp> app));
+ MOCK_METHOD1(OnPaymentAppCreationError,
+ void(const std::string& error_message));
+ MOCK_CONST_METHOD0(SkipCreatingNativePaymentApps, bool());
+ MOCK_METHOD0(OnDoneCreatingPaymentApps, void());
+ MOCK_METHOD0(SetCanMakePaymentEvenWithoutApps, void());
+
+ base::WeakPtr<PaymentAppFactory::Delegate> GetWeakPtr() {
+ return weak_ptr_factory_.GetWeakPtr();
+ }
+
+ private:
+ content::TestWebContentsFactory web_contents_factory_;
+ content::WebContents* web_contents_;
+ GURL top_origin_;
+ GURL frame_origin_;
+ std::unique_ptr<PaymentRequestSpec> spec_;
+ bool is_off_the_record_ = false;
+ base::WeakPtrFactory<PaymentAppFactory::Delegate> weak_ptr_factory_{this};
+};
+
+// The scaffolding for testing the Android payment app factory.
+class AndroidPaymentAppFactoryTest : public testing::Test {
+ public:
+ AndroidPaymentAppFactoryTest()
+ : support_(AndroidAppCommunicationTestSupport::Create()),
+ delegate_(support_->context()),
+ factory_(GetCommunication(support_->context())) {}
+
+ std::unique_ptr<AndroidAppCommunicationTestSupport> support_;
+ MockPaymentAppFactoryDelegate delegate_;
+ AndroidPaymentAppFactory factory_;
+
+ private:
+ // Returns the Android app communication that can be used in unit tests.
+ static base::WeakPtr<AndroidAppCommunication> GetCommunication(
+ content::BrowserContext* context) {
+ base::WeakPtr<AndroidAppCommunication> communication =
+ AndroidAppCommunication::GetForBrowserContext(context);
+ communication->SetForTesting();
+ return communication;
+ }
+};
+
+// The payment app factory should return an error if it's unable to invoke
+// Aneroid payment apps on a platform that supports such apps, e.g, when ARC is
+// disabled on Chrome OS.
+TEST_F(AndroidPaymentAppFactoryTest, FactoryReturnsErrorWithoutArc) {
+ EXPECT_CALL(delegate_, GetTwaPackageName())
+ .WillRepeatedly(testing::Return("com.example.app"));
+ EXPECT_CALL(delegate_, OnDoneCreatingPaymentApps());
+
+ EXPECT_CALL(delegate_,
+ OnPaymentAppCreationError("Unable to invoke Android apps."))
+ .Times(support_->AreAndroidAppsSupportedOnThisPlatform() ? 1 : 0);
+ EXPECT_CALL(delegate_, OnPaymentAppCreated(testing::_)).Times(0);
+ support_->ExpectNoListOfPaymentAppsQuery();
+ support_->ExpectNoIsReadyToPayQuery();
+
+ factory_.Create(delegate_.GetWeakPtr());
+}
+
+// The payment app factory should not return any errors when there're no Android
+// payment apps available.
+TEST_F(AndroidPaymentAppFactoryTest, NoErrorsWhenNoApps) {
+ // Enable invoking Android payment apps on those platforms that support it.
+ auto scoped_initialization_ = support_->CreateScopedInitialization();
+
+ EXPECT_CALL(delegate_, GetTwaPackageName())
+ .WillRepeatedly(testing::Return("com.example.app"));
+ EXPECT_CALL(delegate_, OnDoneCreatingPaymentApps());
+
+ EXPECT_CALL(delegate_, OnPaymentAppCreationError(testing::_)).Times(0);
+ EXPECT_CALL(delegate_, OnPaymentAppCreated(testing::_)).Times(0);
+ support_->ExpectQueryListOfPaymentAppsAndRespond({});
+ support_->ExpectNoIsReadyToPayQuery();
+
+ factory_.Create(delegate_.GetWeakPtr());
+}
+
+// The |arg| is of type std::unique_ptr<PaymentApp>.
+MATCHER_P3(PaymentAppMatches, type, package, method, "") {
+ return arg->type() == type && arg->GetId() == package &&
+ base::Contains(arg->GetAppMethodNames(), method);
+}
+
+// The payment app factory should return the TWA payment app when running in TWA
+// mode, even when it does not have an IS_READY_TO_PAY service.
+TEST_F(AndroidPaymentAppFactoryTest, FindAppsThatDoNotHaveReadyToPayService) {
+ // Enable invoking Android payment apps on those platforms that support it.
+ auto scoped_initialization_ = support_->CreateScopedInitialization();
+
+ EXPECT_CALL(delegate_, GetTwaPackageName())
+ .WillRepeatedly(testing::Return("com.example.app"));
+ EXPECT_CALL(delegate_, OnDoneCreatingPaymentApps());
+
+ EXPECT_CALL(delegate_, OnPaymentAppCreationError(testing::_)).Times(0);
+
+ EXPECT_CALL(delegate_,
+ OnPaymentAppCreated(PaymentAppMatches(
+ PaymentApp::Type::NATIVE_MOBILE_APP, "com.example.app",
+ "https://play.google.com/billing")))
+ .Times(support_->AreAndroidAppsSupportedOnThisPlatform() ? 1 : 0);
+
+ // This app does not have an IS_READY_TO_PAY service.
+ std::vector<std::unique_ptr<AndroidAppDescription>> apps;
+ apps.emplace_back(std::make_unique<AndroidAppDescription>());
+ apps.back()->package = "com.example.app";
+ apps.back()->activities.emplace_back(
+ std::make_unique<AndroidActivityDescription>());
+ apps.back()->activities.back()->name = "com.example.app.Activity";
+ apps.back()->activities.back()->default_payment_method =
+ "https://play.google.com/billing";
+ support_->ExpectQueryListOfPaymentAppsAndRespond(std::move(apps));
+
+ // There is no IS_READY_TO_PAY service to invoke.
+ support_->ExpectNoIsReadyToPayQuery();
+
+ factory_.Create(delegate_.GetWeakPtr());
+}
+
+// The payment app factory should return one payment app and should not query
+// the IS_READY_TO_PAY service, because of being off the record.
+TEST_F(AndroidPaymentAppFactoryTest,
+ DoNotQueryReadyToPaySericeWhenOffTheRecord) {
+ // Enable invoking Android payment apps on those platforms that support it.
+ auto scoped_initialization_ = support_->CreateScopedInitialization();
+
+ // Simulate being off the record.
+ delegate_.set_is_off_the_record();
+
+ EXPECT_CALL(delegate_, GetTwaPackageName())
+ .WillRepeatedly(testing::Return("com.example.app"));
+ EXPECT_CALL(delegate_, OnDoneCreatingPaymentApps());
+
+ EXPECT_CALL(delegate_, OnPaymentAppCreationError(testing::_)).Times(0);
+
+ EXPECT_CALL(delegate_,
+ OnPaymentAppCreated(PaymentAppMatches(
+ PaymentApp::Type::NATIVE_MOBILE_APP, "com.example.app",
+ "https://play.google.com/billing")))
+ .Times(support_->AreAndroidAppsSupportedOnThisPlatform() ? 1 : 0);
+
+ std::vector<std::unique_ptr<AndroidAppDescription>> apps;
+ apps.emplace_back(std::make_unique<AndroidAppDescription>());
+ apps.back()->package = "com.example.app";
+ apps.back()->service_names.push_back("com.example.app.Service");
+ apps.back()->activities.emplace_back(
+ std::make_unique<AndroidActivityDescription>());
+ apps.back()->activities.back()->name = "com.example.app.Activity";
+ apps.back()->activities.back()->default_payment_method =
+ "https://play.google.com/billing";
+ support_->ExpectQueryListOfPaymentAppsAndRespond(std::move(apps));
+
+ // The IS_READY_TO_PAY service should not be invoked when off the record.
+ support_->ExpectNoIsReadyToPayQuery();
+
+ factory_.Create(delegate_.GetWeakPtr());
+}
+
+// The payment app factory should return the TWA payment app that returns true
+// from IS_READY_TO_PAY service when running in TWA mode.
+TEST_F(AndroidPaymentAppFactoryTest,
+ FindTheTwaPaymentAppThatIsReadyToPayInTwaMode) {
+ // Enable invoking Android payment apps on those platforms that support it.
+ auto scoped_initialization_ = support_->CreateScopedInitialization();
+
+ EXPECT_CALL(delegate_, GetTwaPackageName())
+ .WillRepeatedly(testing::Return("com.twa.app"));
+ EXPECT_CALL(delegate_, OnDoneCreatingPaymentApps());
+
+ EXPECT_CALL(delegate_, OnPaymentAppCreationError(testing::_)).Times(0);
+
+ EXPECT_CALL(delegate_, OnPaymentAppCreated(PaymentAppMatches(
+ PaymentApp::Type::NATIVE_MOBILE_APP, "com.twa.app",
+ "https://play.google.com/billing")))
+ .Times(support_->AreAndroidAppsSupportedOnThisPlatform() ? 1 : 0);
+
+ std::vector<std::unique_ptr<AndroidAppDescription>> apps;
+ apps.emplace_back(std::make_unique<AndroidAppDescription>());
+ apps.back()->package = "com.twa.app";
+ apps.back()->service_names.push_back("com.twa.app.Service");
+ apps.back()->activities.emplace_back(
+ std::make_unique<AndroidActivityDescription>());
+ apps.back()->activities.back()->name = "com.twa.app.Activity";
+ apps.back()->activities.back()->default_payment_method =
+ "https://play.google.com/billing";
+ support_->ExpectQueryListOfPaymentAppsAndRespond(std::move(apps));
+ support_->ExpectQueryIsReadyToPayAndRespond(/*is_ready_to_pay=*/true);
+
+ factory_.Create(delegate_.GetWeakPtr());
+}
+
+// The payment app factory should return no payment apps when IS_READY_TO_PAY
+// service returns false.
+TEST_F(AndroidPaymentAppFactoryTest, IgnoreAppsThatAreNotReadyToPay) {
+ // Enable invoking Android payment apps on those platforms that support it.
+ auto scoped_initialization_ = support_->CreateScopedInitialization();
+
+ EXPECT_CALL(delegate_, GetTwaPackageName())
+ .WillRepeatedly(testing::Return("com.example.app"));
+ EXPECT_CALL(delegate_, OnDoneCreatingPaymentApps());
+ EXPECT_CALL(delegate_, OnPaymentAppCreationError(testing::_)).Times(0);
+ EXPECT_CALL(delegate_, OnPaymentAppCreated(testing::_)).Times(0);
+
+ std::vector<std::unique_ptr<AndroidAppDescription>> apps;
+ apps.emplace_back(std::make_unique<AndroidAppDescription>());
+ apps.back()->package = "com.example.app";
+ apps.back()->service_names.push_back("com.example.app.Service");
+ apps.back()->activities.emplace_back(
+ std::make_unique<AndroidActivityDescription>());
+ apps.back()->activities.back()->name = "com.example.app.Activity";
+ apps.back()->activities.back()->default_payment_method =
+ "https://play.google.com/billing";
+ support_->ExpectQueryListOfPaymentAppsAndRespond(std::move(apps));
+ support_->ExpectQueryIsReadyToPayAndRespond(/*is_ready_to_pay=*/false);
+
+ factory_.Create(delegate_.GetWeakPtr());
+}
+
+// The payment app factory should return the correct TWA payment app out of two
+// installed payment apps, when running in TWA mode.
+TEST_F(AndroidPaymentAppFactoryTest, FindTheCorrectTwaAppInTwaMode) {
+ // Enable invoking Android payment apps on those platforms that support it.
+ auto scoped_initialization_ = support_->CreateScopedInitialization();
+
+ EXPECT_CALL(delegate_, GetTwaPackageName())
+ .WillRepeatedly(testing::Return("com.correct-twa.app"));
+ EXPECT_CALL(delegate_, OnDoneCreatingPaymentApps());
+
+ EXPECT_CALL(delegate_, OnPaymentAppCreationError(testing::_)).Times(0);
+
+ EXPECT_CALL(delegate_,
+ OnPaymentAppCreated(PaymentAppMatches(
+ PaymentApp::Type::NATIVE_MOBILE_APP, "com.correct-twa.app",
+ "https://play.google.com/billing")))
+ .Times(support_->AreAndroidAppsSupportedOnThisPlatform() ? 1 : 0);
+ EXPECT_CALL(delegate_,
+ OnPaymentAppCreated(PaymentAppMatches(
+ PaymentApp::Type::NATIVE_MOBILE_APP, "com.different.app",
+ "https://play.google.com/billing")))
+ .Times(0);
+
+ std::vector<std::unique_ptr<AndroidAppDescription>> apps;
+ apps.emplace_back(std::make_unique<AndroidAppDescription>());
+ apps.back()->package = "com.correct-twa.app";
+ apps.back()->service_names.push_back("com.correct-twa.app.Service");
+ apps.back()->activities.emplace_back(
+ std::make_unique<AndroidActivityDescription>());
+ apps.back()->activities.back()->name = "com.correct-twa.app.Activity";
+ apps.back()->activities.back()->default_payment_method =
+ "https://play.google.com/billing";
+
+ apps.emplace_back(std::make_unique<AndroidAppDescription>());
+ apps.back()->package = "com.different.app";
+ apps.back()->service_names.push_back("com.different.app.Service");
+ apps.back()->activities.emplace_back(
+ std::make_unique<AndroidActivityDescription>());
+ apps.back()->activities.back()->name = "com.different.app.Activity";
+ apps.back()->activities.back()->default_payment_method =
+ "https://play.google.com/billing";
+
+ support_->ExpectQueryListOfPaymentAppsAndRespond(std::move(apps));
+ support_->ExpectQueryIsReadyToPayAndRespond(/*is_ready_to_pay=*/true);
+
+ factory_.Create(delegate_.GetWeakPtr());
+}
+
+// The payment app factory does not return non-TWA payment apps when running in
+// TWA mode.
+TEST_F(AndroidPaymentAppFactoryTest, IgnoreNonTwaAppsInTwaMode) {
+ // Enable invoking Android payment apps on those platforms that support it.
+ auto scoped_initialization_ = support_->CreateScopedInitialization();
+
+ EXPECT_CALL(delegate_, GetTwaPackageName())
+ .WillRepeatedly(testing::Return("com.twa.app"));
+ EXPECT_CALL(delegate_, OnDoneCreatingPaymentApps());
+ EXPECT_CALL(delegate_, OnPaymentAppCreationError(testing::_)).Times(0);
+ EXPECT_CALL(delegate_, OnPaymentAppCreated(testing::_)).Times(0);
+
+ std::vector<std::unique_ptr<AndroidAppDescription>> apps;
+ apps.emplace_back(std::make_unique<AndroidAppDescription>());
+ apps.back()->package = "com.non-twa.app";
+ apps.back()->service_names.push_back("com.non-twa.app.Service");
+ apps.back()->activities.emplace_back(
+ std::make_unique<AndroidActivityDescription>());
+ apps.back()->activities.back()->name = "com.non-twa.app.Activity";
+ apps.back()->activities.back()->default_payment_method =
+ "https://play.google.com/billing";
+ support_->ExpectQueryListOfPaymentAppsAndRespond(std::move(apps));
+ support_->ExpectNoIsReadyToPayQuery();
+
+ factory_.Create(delegate_.GetWeakPtr());
+}
+
+// The payment app factory does not return any payment apps when not running
+// inside of TWA.
+TEST_F(AndroidPaymentAppFactoryTest, DoNotLookForAppsWhenOutsideOfTwaMode) {
+ // Enable invoking Android payment apps on those platforms that support it.
+ auto scoped_initialization_ = support_->CreateScopedInitialization();
+
+ EXPECT_CALL(delegate_, GetTwaPackageName())
+ .WillRepeatedly(testing::Return(""));
+ EXPECT_CALL(delegate_, OnDoneCreatingPaymentApps());
+ EXPECT_CALL(delegate_, OnPaymentAppCreationError(testing::_)).Times(0);
+ EXPECT_CALL(delegate_, OnPaymentAppCreated(testing::_)).Times(0);
+ support_->ExpectNoListOfPaymentAppsQuery();
+
+ factory_.Create(delegate_.GetWeakPtr());
+}
+
+// The Android payment app factory works only with TWA specific payment methods.
+TEST_F(AndroidPaymentAppFactoryTest, DoNotLookForAppsForNonTwaMethod) {
+ // Enable invoking Android payment apps on those platforms that support it.
+ auto scoped_initialization_ = support_->CreateScopedInitialization();
+
+ // "https://example.test" is not a TWA specific payment method.
+ delegate_.SetRequestedPaymentMethod("https://example.test");
+
+ EXPECT_CALL(delegate_, GetTwaPackageName())
+ .WillRepeatedly(testing::Return("com.example.app"));
+ EXPECT_CALL(delegate_, OnDoneCreatingPaymentApps());
+ EXPECT_CALL(delegate_, OnPaymentAppCreationError(testing::_)).Times(0);
+ EXPECT_CALL(delegate_, OnPaymentAppCreated(testing::_)).Times(0);
+ support_->ExpectNoListOfPaymentAppsQuery();
+ support_->ExpectNoIsReadyToPayQuery();
+
+ factory_.Create(delegate_.GetWeakPtr());
+}
+
+// If the TWA supports a non-TWA-specific payment method, then it should be
+// ignored.
+TEST_F(AndroidPaymentAppFactoryTest, IgnoreNonTwaMethodInTheTwa) {
+ // Enable invoking Android payment apps on those platforms that support it.
+ auto scoped_initialization_ = support_->CreateScopedInitialization();
+
+ EXPECT_CALL(delegate_, GetTwaPackageName())
+ .WillRepeatedly(testing::Return("com.twa.app"));
+ EXPECT_CALL(delegate_, OnDoneCreatingPaymentApps());
+ EXPECT_CALL(delegate_, OnPaymentAppCreationError(testing::_)).Times(0);
+ EXPECT_CALL(delegate_, OnPaymentAppCreated(testing::_)).Times(0);
+
+ std::vector<std::unique_ptr<AndroidAppDescription>> apps;
+ apps.emplace_back(std::make_unique<AndroidAppDescription>());
+ apps.back()->package = "com.twa.app";
+ apps.back()->service_names.push_back("com.twa.app.Service");
+ apps.back()->activities.emplace_back(
+ std::make_unique<AndroidActivityDescription>());
+ apps.back()->activities.back()->name = "com.twa.app.Activity";
+ apps.back()->activities.back()->default_payment_method =
+ "https://example.test";
+ support_->ExpectQueryListOfPaymentAppsAndRespond(std::move(apps));
+ support_->ExpectNoIsReadyToPayQuery();
+
+ factory_.Create(delegate_.GetWeakPtr());
+}
+
+// If the TWA supports both a TWA-specific and a non-TWA-specific payment
+// method, then only the TWA-specific payment method activity should be
+// returned.
+TEST_F(AndroidPaymentAppFactoryTest,
+ FindOnlyActivitiesWithTwaSpecificMethodName) {
+ // Enable invoking Android payment apps on those platforms that support it.
+ auto scoped_initialization_ = support_->CreateScopedInitialization();
+
+ EXPECT_CALL(delegate_, GetTwaPackageName())
+ .WillRepeatedly(testing::Return("com.twa.app"));
+ EXPECT_CALL(delegate_, OnDoneCreatingPaymentApps());
+ EXPECT_CALL(delegate_, OnPaymentAppCreationError(testing::_)).Times(0);
+ EXPECT_CALL(delegate_, OnPaymentAppCreated(PaymentAppMatches(
+ PaymentApp::Type::NATIVE_MOBILE_APP, "com.twa.app",
+ "https://play.google.com/billing")))
+ .Times(support_->AreAndroidAppsSupportedOnThisPlatform() ? 1 : 0);
+ EXPECT_CALL(delegate_, OnPaymentAppCreated(PaymentAppMatches(
+ PaymentApp::Type::NATIVE_MOBILE_APP, "com.twa.app",
+ "https://example.test")))
+ .Times(0);
+
+ std::vector<std::unique_ptr<AndroidAppDescription>> apps;
+ apps.emplace_back(std::make_unique<AndroidAppDescription>());
+ apps.back()->package = "com.twa.app";
+ apps.back()->service_names.push_back("com.twa.app.Service");
+
+ apps.back()->activities.emplace_back(
+ std::make_unique<AndroidActivityDescription>());
+ apps.back()->activities.back()->name = "com.twa.app.ActivityOne";
+ apps.back()->activities.back()->default_payment_method =
+ "https://play.google.com/billing";
+
+ apps.back()->activities.emplace_back(
+ std::make_unique<AndroidActivityDescription>());
+ apps.back()->activities.back()->name = "com.twa.app.ActivityTwo";
+ apps.back()->activities.back()->default_payment_method =
+ "https://example.test";
+
+ support_->ExpectQueryListOfPaymentAppsAndRespond(std::move(apps));
+ support_->ExpectQueryIsReadyToPayAndRespond(/*is_ready_to_pay=*/true);
+
+ factory_.Create(delegate_.GetWeakPtr());
+}
+
+// At most one IS_READY_TO_PAY service is allowed in an Android payment app.
+TEST_F(AndroidPaymentAppFactoryTest, ReturnErrorWhenMoreThanOneServiceInApp) {
+ // Enable invoking Android payment apps on those platforms that support it.
+ auto scoped_initialization_ = support_->CreateScopedInitialization();
+
+ EXPECT_CALL(delegate_, GetTwaPackageName())
+ .WillRepeatedly(testing::Return("com.example.app"));
+ EXPECT_CALL(delegate_, OnDoneCreatingPaymentApps());
+
+ EXPECT_CALL(delegate_, OnPaymentAppCreationError(
+ "Found more than one IS_READY_TO_PAY service, but "
+ "at most one service is supported."))
+ .Times(support_->AreAndroidAppsSupportedOnThisPlatform() ? 1 : 0);
+
+ EXPECT_CALL(delegate_, OnPaymentAppCreated(testing::_)).Times(0);
+
+ std::vector<std::unique_ptr<AndroidAppDescription>> apps;
+ apps.emplace_back(std::make_unique<AndroidAppDescription>());
+ apps.back()->package = "com.example.app";
+
+ // Two IS_READY_TO_PAY services:
+ apps.back()->service_names.push_back("com.example.app.ServiceOne");
+ apps.back()->service_names.push_back("com.example.app.ServiceTwo");
+
+ apps.back()->activities.emplace_back(
+ std::make_unique<AndroidActivityDescription>());
+ apps.back()->activities.back()->name = "com.example.app.Activity";
+ apps.back()->activities.back()->default_payment_method =
+ "https://play.google.com/billing";
+ support_->ExpectQueryListOfPaymentAppsAndRespond(std::move(apps));
+ support_->ExpectNoIsReadyToPayQuery();
+
+ factory_.Create(delegate_.GetWeakPtr());
+}
+
+} // namespace
+} // namespace payments
diff --git a/chromium/components/payments/content/android_payment_app_unittest.cc b/chromium/components/payments/content/android_payment_app_unittest.cc
new file mode 100644
index 00000000000..cb515a09d53
--- /dev/null
+++ b/chromium/components/payments/content/android_payment_app_unittest.cc
@@ -0,0 +1,164 @@
+// Copyright 2020 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/content/android_payment_app.h"
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/optional.h"
+#include "components/payments/content/android_app_communication.h"
+#include "components/payments/content/android_app_communication_test_support.h"
+#include "components/payments/core/android_app_description.h"
+#include "components/payments/core/method_strings.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace payments {
+namespace {
+
+class AndroidPaymentAppTest : public testing::Test,
+ public PaymentApp::Delegate {
+ public:
+ static std::unique_ptr<AndroidPaymentApp> CreateAndroidPaymentApp(
+ base::WeakPtr<AndroidAppCommunication> communication) {
+ std::set<std::string> payment_method_names;
+ payment_method_names.insert(methods::kGooglePlayBilling);
+ auto stringified_method_data =
+ std::make_unique<std::map<std::string, std::set<std::string>>>();
+ stringified_method_data->insert({methods::kGooglePlayBilling, {"{}"}});
+ auto description = std::make_unique<AndroidAppDescription>();
+ description->package = "com.example.app";
+ description->service_names.push_back("com.example.app.Service");
+ description->activities.emplace_back(
+ std::make_unique<AndroidActivityDescription>());
+ description->activities.back()->name = "com.example.app.Activity";
+ description->activities.back()->default_payment_method =
+ methods::kGooglePlayBilling;
+
+ return std::make_unique<AndroidPaymentApp>(
+ payment_method_names, std::move(stringified_method_data),
+ GURL("https://top-level-origin.com"),
+ GURL("https://payment-request-origin.com"), "payment-request-id",
+ std::move(description), communication);
+ }
+
+ AndroidPaymentAppTest()
+ : support_(AndroidAppCommunicationTestSupport::Create()) {}
+
+ ~AndroidPaymentAppTest() override = default;
+
+ AndroidPaymentAppTest(const AndroidPaymentAppTest& other) = delete;
+ AndroidPaymentAppTest& operator=(const AndroidPaymentAppTest& other) = delete;
+
+ // PaymentApp::Delegate implementation.
+ void OnInstrumentDetailsReady(const std::string& method_name,
+ const std::string& stringified_details,
+ const PayerData& payer_data) override {
+ method_name_ = method_name;
+ stringified_details_ = stringified_details;
+ }
+
+ // PaymentApp::Delegate implementation.
+ void OnInstrumentDetailsError(const std::string& error_message) override {
+ error_message_ = error_message;
+ }
+
+ std::unique_ptr<AndroidAppCommunicationTestSupport> support_;
+ std::unique_ptr<AndroidAppCommunicationTestSupport::ScopedInitialization>
+ scoped_initialization_;
+ base::WeakPtr<AndroidAppCommunication> communication_;
+ std::string method_name_;
+ std::string stringified_details_;
+ std::string error_message_;
+};
+
+TEST_F(AndroidPaymentAppTest, BrowserShutdown) {
+ // Explicitly do not initialize AndroidAppCommunication. This can happen
+ // during browser shutdown.
+ scoped_initialization_ = support_->CreateScopedInitialization();
+
+ support_->ExpectNoPaymentAppInvoke();
+
+ auto app = CreateAndroidPaymentApp(communication_);
+ app->InvokePaymentApp(/*delegate=*/this);
+
+ EXPECT_TRUE(error_message_.empty());
+ EXPECT_TRUE(method_name_.empty());
+ EXPECT_TRUE(stringified_details_.empty());
+}
+
+TEST_F(AndroidPaymentAppTest, UnableToCommunicateToAndroidApps) {
+ communication_ =
+ AndroidAppCommunication::GetForBrowserContext(support_->context());
+ communication_->SetForTesting();
+ // Explicitly do not create ScopedInitialization.
+
+ support_->ExpectNoPaymentAppInvoke();
+
+ auto app = CreateAndroidPaymentApp(communication_);
+ app->InvokePaymentApp(/*delegate=*/this);
+
+ EXPECT_EQ("Unable to invoke Android apps.", error_message_);
+ EXPECT_TRUE(method_name_.empty());
+ EXPECT_TRUE(stringified_details_.empty());
+}
+
+TEST_F(AndroidPaymentAppTest, OnInstrumentDetailsError) {
+ communication_ =
+ AndroidAppCommunication::GetForBrowserContext(support_->context());
+ communication_->SetForTesting();
+ scoped_initialization_ = support_->CreateScopedInitialization();
+
+ support_->ExpectInvokePaymentAppAndRespond(
+ /*is_activity_result_ok=*/false,
+ /*payment_method_identifier=*/methods::kGooglePlayBilling,
+ /*stringified_details=*/"{}");
+
+ auto app = CreateAndroidPaymentApp(communication_);
+ app->InvokePaymentApp(/*delegate=*/this);
+
+ if (support_->AreAndroidAppsSupportedOnThisPlatform()) {
+ EXPECT_EQ("User closed the payment app.", error_message_);
+ } else {
+ EXPECT_EQ("Unable to invoke Android apps.", error_message_);
+ }
+
+ EXPECT_TRUE(method_name_.empty());
+ EXPECT_TRUE(stringified_details_.empty());
+}
+
+TEST_F(AndroidPaymentAppTest, OnInstrumentDetailsReady) {
+ communication_ =
+ AndroidAppCommunication::GetForBrowserContext(support_->context());
+ communication_->SetForTesting();
+ scoped_initialization_ = support_->CreateScopedInitialization();
+
+ support_->ExpectInvokePaymentAppAndRespond(
+ /*is_activity_result_ok=*/true,
+ /*payment_method_identifier=*/methods::kGooglePlayBilling,
+ /*stringified_details=*/"{\"status\": \"ok\"}");
+
+ auto app = CreateAndroidPaymentApp(communication_);
+ app->InvokePaymentApp(/*delegate=*/this);
+
+ if (support_->AreAndroidAppsSupportedOnThisPlatform()) {
+ EXPECT_TRUE(error_message_.empty());
+ EXPECT_EQ(methods::kGooglePlayBilling, method_name_);
+ EXPECT_EQ("{\"status\": \"ok\"}", stringified_details_);
+ } else {
+ EXPECT_EQ("Unable to invoke Android apps.", error_message_);
+ EXPECT_TRUE(method_name_.empty());
+ EXPECT_TRUE(stringified_details_.empty());
+ }
+}
+
+} // namespace
+} // namespace payments
diff --git a/chromium/components/payments/content/autofill_payment_app_factory.cc b/chromium/components/payments/content/autofill_payment_app_factory.cc
index b6eadc5e02c..dafa63818f8 100644
--- a/chromium/components/payments/content/autofill_payment_app_factory.cc
+++ b/chromium/components/payments/content/autofill_payment_app_factory.cc
@@ -21,6 +21,9 @@ std::unique_ptr<PaymentApp>
AutofillPaymentAppFactory::ConvertCardToPaymentAppIfSupportedNetwork(
const autofill::CreditCard& card,
base::WeakPtr<Delegate> delegate) {
+ DCHECK(delegate);
+ DCHECK(delegate->GetSpec());
+
std::string basic_card_network =
autofill::data_util::GetPaymentRequestData(card.network())
.basic_card_issuer_network;
@@ -46,6 +49,11 @@ AutofillPaymentAppFactory::AutofillPaymentAppFactory()
AutofillPaymentAppFactory::~AutofillPaymentAppFactory() = default;
void AutofillPaymentAppFactory::Create(base::WeakPtr<Delegate> delegate) {
+ DCHECK(delegate);
+
+ if (!delegate->GetSpec())
+ return;
+
// No need to create autofill payment apps if native app creation is skipped
// because autofill payment apps are created completely by the Java factory.
if (delegate->SkipCreatingNativePaymentApps()) {
diff --git a/chromium/components/payments/content/autofill_payment_app_unittest.cc b/chromium/components/payments/content/autofill_payment_app_unittest.cc
index acaf906122c..a465d7d3636 100644
--- a/chromium/components/payments/content/autofill_payment_app_unittest.cc
+++ b/chromium/components/payments/content/autofill_payment_app_unittest.cc
@@ -21,7 +21,6 @@
#include "components/autofill/core/browser/test_personal_data_manager.h"
#include "components/payments/core/test_payment_request_delegate.h"
#include "components/strings/grit/components_strings.h"
-#include "net/url_request/url_request_test_util.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"
diff --git a/chromium/components/payments/content/content_payment_request_delegate.cc b/chromium/components/payments/content/content_payment_request_delegate.cc
new file mode 100644
index 00000000000..65b8d7c99c1
--- /dev/null
+++ b/chromium/components/payments/content/content_payment_request_delegate.cc
@@ -0,0 +1,18 @@
+// Copyright 2020 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/content/content_payment_request_delegate.h"
+
+namespace payments {
+
+ContentPaymentRequestDelegate::~ContentPaymentRequestDelegate() = default;
+
+base::WeakPtr<ContentPaymentRequestDelegate>
+ContentPaymentRequestDelegate::GetContentWeakPtr() {
+ return weak_ptr_factory_.GetWeakPtr();
+}
+
+ContentPaymentRequestDelegate::ContentPaymentRequestDelegate() = default;
+
+} // namespace payments
diff --git a/chromium/components/payments/content/content_payment_request_delegate.h b/chromium/components/payments/content/content_payment_request_delegate.h
index c6db3f4215b..60bef446b9f 100644
--- a/chromium/components/payments/content/content_payment_request_delegate.h
+++ b/chromium/components/payments/content/content_payment_request_delegate.h
@@ -5,23 +5,35 @@
#ifndef COMPONENTS_PAYMENTS_CONTENT_CONTENT_PAYMENT_REQUEST_DELEGATE_H_
#define COMPONENTS_PAYMENTS_CONTENT_CONTENT_PAYMENT_REQUEST_DELEGATE_H_
+#include <memory>
#include <string>
+#include "base/memory/weak_ptr.h"
#include "components/payments/content/payment_request_display_manager.h"
#include "components/payments/core/payment_request_delegate.h"
template <class T>
class scoped_refptr;
+namespace autofill {
+class InternalAuthenticator;
+} // namespace autofill
+
namespace payments {
class PaymentManifestWebDataService;
+class PaymentRequestDialog;
class PaymentRequestDisplayManager;
// The delegate for PaymentRequest that can use content.
class ContentPaymentRequestDelegate : public PaymentRequestDelegate {
public:
- ~ContentPaymentRequestDelegate() override {}
+ ~ContentPaymentRequestDelegate() override;
+
+ // Creates and returns an instance of the InternalAuthenticator interface for
+ // communication with WebAuthn.
+ virtual std::unique_ptr<autofill::InternalAuthenticator>
+ CreateInternalAuthenticator() const = 0;
// Returns the web data service for caching payment method manifests.
virtual scoped_refptr<PaymentManifestWebDataService>
@@ -59,6 +71,17 @@ class ContentPaymentRequestDelegate : public PaymentRequestDelegate {
// Returns the Android package name of the Trusted Web Activity that invoked
// this browser, if any. Otherwise, an empty string.
virtual std::string GetTwaPackageName() const = 0;
+
+ virtual PaymentRequestDialog* GetDialogForTesting() = 0;
+
+ // Returns a weak pointer to this delegate.
+ base::WeakPtr<ContentPaymentRequestDelegate> GetContentWeakPtr();
+
+ protected:
+ ContentPaymentRequestDelegate();
+
+ private:
+ base::WeakPtrFactory<ContentPaymentRequestDelegate> weak_ptr_factory_{this};
};
} // namespace payments
diff --git a/chromium/components/payments/content/icon/BUILD.gn b/chromium/components/payments/content/icon/BUILD.gn
index b031c0b8bd1..a5d3326f4c3 100644
--- a/chromium/components/payments/content/icon/BUILD.gn
+++ b/chromium/components/payments/content/icon/BUILD.gn
@@ -2,9 +2,7 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-import("//build/config/jumbo.gni")
-
-jumbo_static_library("icon") {
+static_library("icon") {
sources = [
"icon_size.cc",
"icon_size.h",
diff --git a/chromium/components/payments/content/manifest_verifier.cc b/chromium/components/payments/content/manifest_verifier.cc
index 9529b48a2e2..5800ab987e9 100644
--- a/chromium/components/payments/content/manifest_verifier.cc
+++ b/chromium/components/payments/content/manifest_verifier.cc
@@ -32,7 +32,7 @@ namespace {
void EnableMethodManifestUrlForSupportedApps(
const GURL& method_manifest_url,
const std::vector<std::string>& supported_origin_strings,
- content::PaymentAppProvider::PaymentApps* apps,
+ content::InstalledPaymentAppsFinder::PaymentApps* apps,
std::vector<int64_t> app_ids,
std::map<GURL, std::set<GURL>>* prohibited_payment_methods) {
for (auto app_id : app_ids) {
@@ -68,9 +68,10 @@ ManifestVerifier::~ManifestVerifier() {
}
}
-void ManifestVerifier::Verify(content::PaymentAppProvider::PaymentApps apps,
- VerifyCallback finished_verification,
- base::OnceClosure finished_using_resources) {
+void ManifestVerifier::Verify(
+ content::InstalledPaymentAppsFinder::PaymentApps apps,
+ VerifyCallback finished_verification,
+ base::OnceClosure finished_using_resources) {
DCHECK(apps_.empty());
DCHECK(finished_verification_callback_.is_null());
DCHECK(finished_using_resources_callback_.is_null());
diff --git a/chromium/components/payments/content/manifest_verifier.h b/chromium/components/payments/content/manifest_verifier.h
index 6e1207eaca8..0974571665a 100644
--- a/chromium/components/payments/content/manifest_verifier.h
+++ b/chromium/components/payments/content/manifest_verifier.h
@@ -18,6 +18,7 @@
#include "components/payments/content/developer_console_logger.h"
#include "components/webdata/common/web_data_service_base.h"
#include "components/webdata/common/web_data_service_consumer.h"
+#include "content/public/browser/installed_payment_apps_finder.h"
#include "content/public/browser/payment_app_provider.h"
#include "content/public/browser/web_contents_observer.h"
#include "url/origin.h"
@@ -59,7 +60,7 @@ class ManifestVerifier final : public WebDataServiceConsumer {
// remaining. If a payment handler does not have any valid payment method
// names, then it's not included in the returned set of handlers.
using VerifyCallback =
- base::OnceCallback<void(content::PaymentAppProvider::PaymentApps,
+ base::OnceCallback<void(content::InstalledPaymentAppsFinder::PaymentApps,
const std::string& error_message)>;
// Creates the verifier and starts up the parser utility process.
@@ -81,7 +82,7 @@ class ManifestVerifier final : public WebDataServiceConsumer {
// Initiates the verification. This object should be deleted after
// |finished_using_resources| is invoked.
- void Verify(content::PaymentAppProvider::PaymentApps apps,
+ void Verify(content::InstalledPaymentAppsFinder::PaymentApps apps,
VerifyCallback finished_verification,
base::OnceClosure finished_using_resources);
@@ -121,7 +122,7 @@ class ManifestVerifier final : public WebDataServiceConsumer {
PaymentManifestWebDataService* cache_;
// The list of payment apps being verified.
- content::PaymentAppProvider::PaymentApps apps_;
+ content::InstalledPaymentAppsFinder::PaymentApps apps_;
// A mapping from the payment app scope to the set of the URL-based payment
// methods that it claims to support, but is not allowed due to the payment
diff --git a/chromium/components/payments/content/payment_app.cc b/chromium/components/payments/content/payment_app.cc
index 594eba178bb..69f2c695efc 100644
--- a/chromium/components/payments/content/payment_app.cc
+++ b/chromium/components/payments/content/payment_app.cc
@@ -99,6 +99,10 @@ void PaymentApp::AbortPaymentApp(
std::move(abort_callback).Run(/*aborted=*/false);
}
+bool PaymentApp::IsPreferred() const {
+ return false;
+}
+
// static
void PaymentApp::SortApps(std::vector<std::unique_ptr<PaymentApp>>* apps) {
DCHECK(apps);
diff --git a/chromium/components/payments/content/payment_app.h b/chromium/components/payments/content/payment_app.h
index 570038c36b9..5bb070fba9d 100644
--- a/chromium/components/payments/content/payment_app.h
+++ b/chromium/components/payments/content/payment_app.h
@@ -185,6 +185,10 @@ class PaymentApp {
// payment app is currently invoked.
virtual void AbortPaymentApp(base::OnceCallback<void(bool)> abort_callback);
+ // Whether this app should be chosen over other available payment apps. For
+ // example, when the Play Billing payment app is available in a TWA.
+ virtual bool IsPreferred() const;
+
protected:
PaymentApp(int icon_resource_id, Type type);
diff --git a/chromium/components/payments/content/payment_app_factory.h b/chromium/components/payments/content/payment_app_factory.h
index be8a7731ffd..0708bd1c203 100644
--- a/chromium/components/payments/content/payment_app_factory.h
+++ b/chromium/components/payments/content/payment_app_factory.h
@@ -20,6 +20,7 @@ class GURL;
namespace autofill {
class AutofillProfile;
+class InternalAuthenticator;
} // namespace autofill
namespace content {
@@ -51,12 +52,21 @@ class PaymentAppFactory {
virtual content::RenderFrameHost* GetInitiatorRenderFrameHost() const = 0;
virtual const std::vector<mojom::PaymentMethodDataPtr>& GetMethodData()
const = 0;
+ virtual std::unique_ptr<autofill::InternalAuthenticator>
+ CreateInternalAuthenticator() const = 0;
virtual scoped_refptr<PaymentManifestWebDataService>
GetPaymentManifestWebDataService() const = 0;
virtual bool MayCrawlForInstallablePaymentApps() = 0;
virtual bool IsOffTheRecord() const = 0;
+
+ // Returns the merchant provided information, or null if the payment is
+ // being aborted.
virtual PaymentRequestSpec* GetSpec() const = 0;
+ // Returns the Android package name of the Trusted Web Activity that invoked
+ // this browser, if any. Otherwise, an empty string.
+ virtual std::string GetTwaPackageName() const = 0;
+
// Tells the UI to show the processing spinner. Only desktop UI needs this
// notification.
virtual void ShowProcessingSpinner() = 0;
@@ -84,6 +94,14 @@ class PaymentAppFactory {
// Called when all apps of this factory have been created.
virtual void OnDoneCreatingPaymentApps() = 0;
+
+ // Make both canMakePayment() and hasEnrolledInstrument() return true,
+ // regardless of presence of payment apps. This is used by secure payment
+ // confirmation method, which returns true for canMakePayment() and
+ // hasEnrolledInstrument() regardless of presence of credentials in user
+ // profile or the authenticator device, as long as a user-verifying platform
+ // authenticator device is available.
+ virtual void SetCanMakePaymentEvenWithoutApps() = 0;
};
explicit PaymentAppFactory(PaymentApp::Type type);
diff --git a/chromium/components/payments/content/payment_app_service.cc b/chromium/components/payments/content/payment_app_service.cc
index 1dc68fb0b2c..45fb49ea67c 100644
--- a/chromium/components/payments/content/payment_app_service.cc
+++ b/chromium/components/payments/content/payment_app_service.cc
@@ -5,18 +5,40 @@
#include "components/payments/content/payment_app_service.h"
#include "base/feature_list.h"
+#include "components/payments/content/android_app_communication.h"
+#include "components/payments/content/android_payment_app_factory.h"
#include "components/payments/content/autofill_payment_app_factory.h"
#include "components/payments/content/payment_app.h"
+#include "components/payments/content/secure_payment_confirmation_app_factory.h"
#include "components/payments/content/service_worker_payment_app_factory.h"
+#include "components/payments/core/features.h"
+#include "components/payments/core/payments_experimental_features.h"
#include "content/public/common/content_features.h"
namespace payments {
-PaymentAppService::PaymentAppService() {
+PaymentAppService::PaymentAppService(content::BrowserContext* context) {
factories_.emplace_back(std::make_unique<AutofillPaymentAppFactory>());
- if (base::FeatureList::IsEnabled(::features::kServiceWorkerPaymentApps))
- factories_.emplace_back(std::make_unique<ServiceWorkerPaymentAppFactory>());
+ if (base::FeatureList::IsEnabled(::features::kServiceWorkerPaymentApps)) {
+ factories_.push_back(std::make_unique<ServiceWorkerPaymentAppFactory>());
+ }
+
+ // TODO(https://crbug.com/1022512): Review the feature flag name when
+ // AndroidPaymentAppFactory works on Android OS with generic 3rd party payment
+ // apps. (Currently it works only on Chrome OS with app store billing payment
+ // methods.)
+ if (PaymentsExperimentalFeatures::IsEnabled(features::kAppStoreBilling)) {
+ factories_.push_back(std::make_unique<AndroidPaymentAppFactory>(
+ AndroidAppCommunication::GetForBrowserContext(context)));
+ }
+
+ // SecurePaymentConfirmation is enabled if both the feature flag and the Blink
+ // runtime feature "SecurePaymentConfirmation" are enabled.
+ if (base::FeatureList::IsEnabled(features::kSecurePaymentConfirmation)) {
+ factories_.push_back(
+ std::make_unique<SecurePaymentConfirmationAppFactory>());
+ }
}
PaymentAppService::~PaymentAppService() = default;
diff --git a/chromium/components/payments/content/payment_app_service.h b/chromium/components/payments/content/payment_app_service.h
index a26ce3ef423..40f1c18fcdd 100644
--- a/chromium/components/payments/content/payment_app_service.h
+++ b/chromium/components/payments/content/payment_app_service.h
@@ -14,12 +14,17 @@
#include "components/keyed_service/core/keyed_service.h"
#include "components/payments/content/payment_app_factory.h"
+namespace content {
+class BrowserContext;
+} // namespace content
+
namespace payments {
// Retrieves payment apps of all types.
class PaymentAppService : public KeyedService {
public:
- PaymentAppService();
+ // The |context| pointer is not being saved.
+ explicit PaymentAppService(content::BrowserContext* context);
~PaymentAppService() override;
// Returns the number of payment app factories, which is the number of times
diff --git a/chromium/components/payments/content/payment_app_service_factory.cc b/chromium/components/payments/content/payment_app_service_factory.cc
index 3fb5f947b37..686ac505998 100644
--- a/chromium/components/payments/content/payment_app_service_factory.cc
+++ b/chromium/components/payments/content/payment_app_service_factory.cc
@@ -43,7 +43,7 @@ PaymentAppServiceFactory::~PaymentAppServiceFactory() = default;
KeyedService* PaymentAppServiceFactory::BuildServiceInstanceFor(
content::BrowserContext* context) const {
- return new PaymentAppService();
+ return new PaymentAppService(context);
}
content::BrowserContext* PaymentAppServiceFactory::GetBrowserContextToUse(
diff --git a/chromium/components/payments/content/payment_credential.cc b/chromium/components/payments/content/payment_credential.cc
new file mode 100644
index 00000000000..390a402750e
--- /dev/null
+++ b/chromium/components/payments/content/payment_credential.cc
@@ -0,0 +1,129 @@
+// Copyright 2020 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/content/payment_credential.h"
+
+#include <memory>
+
+#include "base/memory/ref_counted_memory.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/payments/content/payment_manifest_web_data_service.h"
+#include "components/payments/core/secure_payment_confirmation_instrument.h"
+#include "components/payments/core/url_util.h"
+#include "content/public/browser/web_contents.h"
+#include "ui/gfx/image/image.h"
+
+namespace payments {
+
+PaymentCredential::PaymentCredential(
+ content::WebContents* web_contents,
+ content::GlobalFrameRoutingId initiator_frame_routing_id,
+ scoped_refptr<PaymentManifestWebDataService> web_data_sevice,
+ mojo::PendingReceiver<mojom::PaymentCredential> receiver)
+ : WebContentsObserver(web_contents),
+ initiator_frame_routing_id_(initiator_frame_routing_id),
+ web_data_service_(web_data_sevice) {
+ DCHECK(web_contents);
+ receiver_.Bind(std::move(receiver));
+}
+
+PaymentCredential::~PaymentCredential() = default;
+
+void PaymentCredential::StorePaymentCredential(
+ payments::mojom::PaymentCredentialInstrumentPtr instrument,
+ const std::vector<uint8_t>& credential_id,
+ const std::string& rp_id,
+ StorePaymentCredentialCallback callback) {
+ if (!web_data_service_) {
+ std::move(callback).Run(
+ mojom::PaymentCredentialCreationStatus::FAILED_TO_STORE_INSTRUMENT);
+ return;
+ }
+
+ if (!web_contents() ||
+ !UrlUtil::IsOriginAllowedToUseWebPaymentApis(instrument->icon)) {
+ std::move(callback).Run(
+ mojom::PaymentCredentialCreationStatus::FAILED_TO_DOWNLOAD_ICON);
+ return;
+ }
+
+ // If the initiator frame doesn't exist any more, e.g. the frame has
+ // navigated away, don't download the icon.
+ content::RenderFrameHost* render_frame_host =
+ content::RenderFrameHost::FromID(initiator_frame_routing_id_);
+ if (!render_frame_host || !render_frame_host->IsCurrent()) {
+ std::move(callback).Run(
+ mojom::PaymentCredentialCreationStatus::FAILED_TO_DOWNLOAD_ICON);
+ return;
+ }
+
+ int request_id = web_contents()->DownloadImageInFrame(
+ initiator_frame_routing_id_,
+ instrument->icon, // source URL
+ true, // is_favicon
+ 0, // no preferred size
+ 0, // no max size
+ false, // normal cache policy (a.k.a. do not bypass cache)
+ base::BindOnce(&PaymentCredential::DidDownloadFavicon,
+ weak_ptr_factory_.GetWeakPtr(), std::move(instrument),
+ credential_id, rp_id, std::move(callback)));
+ pending_icon_download_request_ids_.insert(request_id);
+}
+
+void PaymentCredential::DidDownloadFavicon(
+ payments::mojom::PaymentCredentialInstrumentPtr instrument,
+ const std::vector<uint8_t>& credential_id,
+ const std::string& rp_id,
+ StorePaymentCredentialCallback callback,
+ int request_id,
+ int unused_http_status_code,
+ const GURL& image_url,
+ const std::vector<SkBitmap>& bitmaps,
+ const std::vector<gfx::Size>& unused_sizes) {
+ auto iterator = pending_icon_download_request_ids_.find(request_id);
+ DCHECK(iterator != pending_icon_download_request_ids_.end());
+ pending_icon_download_request_ids_.erase(iterator);
+
+ if (bitmaps.empty()) {
+ std::move(callback).Run(
+ mojom::PaymentCredentialCreationStatus::FAILED_TO_DOWNLOAD_ICON);
+ return;
+ }
+
+ // TODO(https://crbug.com/1110320): Get the best icon using |preferred size|
+ // rather than the first one if multiple downloaded.
+ gfx::Image downloaded_image = gfx::Image::CreateFrom1xBitmap(bitmaps[0]);
+ scoped_refptr<base::RefCountedMemory> raw_data =
+ downloaded_image.As1xPNGBytes();
+ auto encoded_icon =
+ std::vector<uint8_t>(raw_data->front_as<uint8_t>(),
+ raw_data->front_as<uint8_t>() + raw_data->size());
+
+ WebDataServiceBase::Handle handle =
+ web_data_service_->AddSecurePaymentConfirmationInstrument(
+ std::make_unique<SecurePaymentConfirmationInstrument>(
+ credential_id, rp_id, base::UTF8ToUTF16(instrument->display_name),
+ std::move(encoded_icon)),
+ /*consumer=*/this);
+ callbacks_[handle] = std::move(callback);
+}
+
+void PaymentCredential::OnWebDataServiceRequestDone(
+ WebDataServiceBase::Handle h,
+ std::unique_ptr<WDTypedResult> result) {
+ auto iterator = callbacks_.find(h);
+ if (iterator == callbacks_.end())
+ return;
+
+ auto callback = std::move(iterator->second);
+ DCHECK(callback);
+ callbacks_.erase(iterator);
+
+ std::move(callback).Run(
+ static_cast<WDResult<bool>*>(result.get())->GetValue()
+ ? mojom::PaymentCredentialCreationStatus::SUCCESS
+ : mojom::PaymentCredentialCreationStatus::FAILED_TO_STORE_INSTRUMENT);
+}
+
+} // namespace payments
diff --git a/chromium/components/payments/content/payment_credential.h b/chromium/components/payments/content/payment_credential.h
new file mode 100644
index 00000000000..9b646c9e76e
--- /dev/null
+++ b/chromium/components/payments/content/payment_credential.h
@@ -0,0 +1,84 @@
+// Copyright 2020 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_PAYMENT_CREDENTIAL_H_
+#define COMPONENTS_PAYMENTS_CONTENT_PAYMENT_CREDENTIAL_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
+#include "components/webdata/common/web_data_service_base.h"
+#include "components/webdata/common/web_data_service_consumer.h"
+#include "content/public/browser/global_routing_id.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "third_party/blink/public/mojom/payments/payment_credential.mojom.h"
+
+namespace content {
+class WebContents;
+} // namespace content
+
+namespace payments {
+
+class PaymentManifestWebDataService;
+
+// Implementation of the mojom::PaymentCredential interface for storing
+// PaymentCredential instruments and their associated WebAuthn credential IDs.
+// These can be retrieved later to authenticate during a PaymentRequest
+// that uses Secure Payment Confirmation.
+class PaymentCredential : public mojom::PaymentCredential,
+ public WebDataServiceConsumer,
+ public content::WebContentsObserver {
+ public:
+ PaymentCredential(
+ content::WebContents* web_contents,
+ content::GlobalFrameRoutingId initiator_frame_routing_id,
+ scoped_refptr<PaymentManifestWebDataService> web_data_sevice,
+ mojo::PendingReceiver<mojom::PaymentCredential> receiver);
+ ~PaymentCredential() override;
+
+ PaymentCredential(const PaymentCredential&) = delete;
+ PaymentCredential& operator=(const PaymentCredential&) = delete;
+
+ // mojom::PaymentCredential:
+ void StorePaymentCredential(
+ payments::mojom::PaymentCredentialInstrumentPtr instrument,
+ const std::vector<uint8_t>& credential_id,
+ const std::string& rp_id,
+ StorePaymentCredentialCallback callback) override;
+
+ private:
+ // WebDataServiceConsumer:
+ void OnWebDataServiceRequestDone(
+ WebDataServiceBase::Handle h,
+ std::unique_ptr<WDTypedResult> result) override;
+
+ void DidDownloadFavicon(
+ payments::mojom::PaymentCredentialInstrumentPtr instrument,
+ const std::vector<uint8_t>& credential_id,
+ const std::string& rp_id,
+ StorePaymentCredentialCallback callback,
+ int request_id,
+ int unused_http_status_code,
+ const GURL& image_url,
+ const std::vector<SkBitmap>& bitmaps,
+ const std::vector<gfx::Size>& unused_sizes);
+
+ const content::GlobalFrameRoutingId initiator_frame_routing_id_;
+ scoped_refptr<PaymentManifestWebDataService> web_data_service_;
+ std::map<WebDataServiceBase::Handle, StorePaymentCredentialCallback>
+ callbacks_;
+ mojo::Receiver<mojom::PaymentCredential> receiver_{this};
+ std::set<int> pending_icon_download_request_ids_;
+ base::WeakPtrFactory<PaymentCredential> weak_ptr_factory_{this};
+};
+
+} // namespace payments
+
+#endif // COMPONENTS_PAYMENTS_CONTENT_PAYMENT_CREDENTIAL_H_
diff --git a/chromium/components/payments/content/payment_handler_host.cc b/chromium/components/payments/content/payment_handler_host.cc
index 4779d9b5b11..0ef89baaf68 100644
--- a/chromium/components/payments/content/payment_handler_host.cc
+++ b/chromium/components/payments/content/payment_handler_host.cc
@@ -23,11 +23,15 @@ namespace payments {
namespace {
content::DevToolsBackgroundServicesContext* GetDevTools(
- content::BrowserContext* browser_context,
+ content::WebContents* web_contents,
const url::Origin& sw_origin) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ if (!web_contents)
+ return nullptr;
+
auto* storage_partition = content::BrowserContext::GetStoragePartitionForSite(
- browser_context, sw_origin.GetURL(), /*can_create=*/true);
+ web_contents->GetBrowserContext(), sw_origin.GetURL(),
+ /*can_create=*/true);
if (!storage_partition)
return nullptr;
@@ -52,8 +56,8 @@ void RunCallbackWithError(const std::string& error,
PaymentHandlerHost::PaymentHandlerHost(content::WebContents* web_contents,
Delegate* delegate)
- : web_contents_(web_contents), delegate_(delegate) {
- DCHECK(web_contents_);
+ : WebContentsObserver(web_contents), delegate_(delegate) {
+ DCHECK(web_contents);
DCHECK(delegate_);
}
@@ -76,8 +80,7 @@ void PaymentHandlerHost::UpdateWith(
if (!change_payment_request_details_callback_)
return;
- auto* dev_tools =
- GetDevTools(web_contents_->GetBrowserContext(), sw_origin_for_logs_);
+ auto* dev_tools = GetDevTools(web_contents(), sw_origin_for_logs_);
if (dev_tools) {
std::map<std::string, std::string> data = {{"Error", response->error}};
@@ -195,8 +198,7 @@ void PaymentHandlerHost::ChangePaymentMethod(
return;
}
- auto* dev_tools =
- GetDevTools(web_contents_->GetBrowserContext(), sw_origin_for_logs_);
+ auto* dev_tools = GetDevTools(web_contents(), sw_origin_for_logs_);
if (dev_tools) {
dev_tools->LogBackgroundServiceEvent(
registration_id_for_logs_, sw_origin_for_logs_,
@@ -226,8 +228,7 @@ void PaymentHandlerHost::ChangeShippingOption(
return;
}
- auto* dev_tools =
- GetDevTools(web_contents_->GetBrowserContext(), sw_origin_for_logs_);
+ auto* dev_tools = GetDevTools(web_contents(), sw_origin_for_logs_);
if (dev_tools) {
dev_tools->LogBackgroundServiceEvent(
registration_id_for_logs_, sw_origin_for_logs_,
@@ -256,8 +257,7 @@ void PaymentHandlerHost::ChangeShippingAddress(
return;
}
- auto* dev_tools =
- GetDevTools(web_contents_->GetBrowserContext(), sw_origin_for_logs_);
+ auto* dev_tools = GetDevTools(web_contents(), sw_origin_for_logs_);
if (dev_tools) {
std::map<std::string, std::string> shipping_address_map;
shipping_address_map.emplace("Country", shipping_address->country);
diff --git a/chromium/components/payments/content/payment_handler_host.h b/chromium/components/payments/content/payment_handler_host.h
index c3814c48489..435f33e65c2 100644
--- a/chromium/components/payments/content/payment_handler_host.h
+++ b/chromium/components/payments/content/payment_handler_host.h
@@ -11,6 +11,7 @@
#include "base/callback_forward.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
+#include "content/public/browser/web_contents_observer.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "third_party/blink/public/mojom/payments/payment_handler_host.mojom.h"
@@ -27,7 +28,8 @@ using ChangePaymentRequestDetailsCallback =
// Handles the communication from the payment handler renderer process to the
// merchant renderer process.
-class PaymentHandlerHost : public mojom::PaymentHandlerHost {
+class PaymentHandlerHost : public mojom::PaymentHandlerHost,
+ public content::WebContentsObserver {
public:
// The interface to be implemented by the object that can communicate to the
// merchant's renderer process.
@@ -127,9 +129,6 @@ class PaymentHandlerHost : public mojom::PaymentHandlerHost {
// browser process.
mojo::Receiver<mojom::PaymentHandlerHost> receiver_{this};
- // The merchant page that invoked the Payment Request API.
- content::WebContents* web_contents_;
-
// Not null and outlives this object. Either owns this object or is owned by
// the owner of this object.
Delegate* delegate_;
diff --git a/chromium/components/payments/content/payment_manifest_web_data_service.cc b/chromium/components/payments/content/payment_manifest_web_data_service.cc
index 8294b9eaac2..7842bf448f4 100644
--- a/chromium/components/payments/content/payment_manifest_web_data_service.cc
+++ b/chromium/components/payments/content/payment_manifest_web_data_service.cc
@@ -4,10 +4,13 @@
#include "components/payments/content/payment_manifest_web_data_service.h"
+#include <utility>
+
#include "base/bind.h"
#include "base/location.h"
#include "components/payments/content/payment_method_manifest_table.h"
#include "components/payments/content/web_app_manifest_section_table.h"
+#include "components/payments/core/secure_payment_confirmation_instrument.h"
#include "components/webdata/common/web_data_results.h"
#include "components/webdata/common/web_database_service.h"
@@ -108,6 +111,51 @@ PaymentManifestWebDataService::GetPaymentMethodManifestImpl(
payment_method));
}
+WebDataServiceBase::Handle
+PaymentManifestWebDataService::AddSecurePaymentConfirmationInstrument(
+ std::unique_ptr<SecurePaymentConfirmationInstrument> instrument,
+ WebDataServiceConsumer* consumer) {
+ DCHECK(instrument);
+ return wdbs_->ScheduleDBTaskWithResult(
+ FROM_HERE,
+ base::BindOnce(&PaymentManifestWebDataService::
+ AddSecurePaymentConfirmationInstrumentImpl,
+ this, std::move(instrument)),
+ consumer);
+}
+
+std::unique_ptr<WDTypedResult>
+PaymentManifestWebDataService::AddSecurePaymentConfirmationInstrumentImpl(
+ std::unique_ptr<SecurePaymentConfirmationInstrument> instrument,
+ WebDatabase* db) {
+ return std::make_unique<WDResult<bool>>(
+ BOOL_RESULT, PaymentMethodManifestTable::FromWebDatabase(db)
+ ->AddSecurePaymentConfirmationInstrument(*instrument));
+}
+
+WebDataServiceBase::Handle
+PaymentManifestWebDataService::GetSecurePaymentConfirmationInstruments(
+ std::vector<std::vector<uint8_t>> credential_ids,
+ WebDataServiceConsumer* consumer) {
+ return wdbs_->ScheduleDBTaskWithResult(
+ FROM_HERE,
+ base::BindOnce(&PaymentManifestWebDataService::
+ GetSecurePaymentConfirmationInstrumentsImpl,
+ this, std::move(credential_ids)),
+ consumer);
+}
+
+std::unique_ptr<WDTypedResult>
+PaymentManifestWebDataService::GetSecurePaymentConfirmationInstrumentsImpl(
+ std::vector<std::vector<uint8_t>> credential_ids,
+ WebDatabase* db) {
+ return std::make_unique<WDResult<
+ std::vector<std::unique_ptr<SecurePaymentConfirmationInstrument>>>>(
+ SECURE_PAYMENT_CONFIRMATION,
+ PaymentMethodManifestTable::FromWebDatabase(db)
+ ->GetSecurePaymentConfirmationInstruments(std::move(credential_ids)));
+}
+
void PaymentManifestWebDataService::RemoveExpiredData(WebDatabase* db) {
PaymentMethodManifestTable::FromWebDatabase(db)->RemoveExpiredData();
WebAppManifestSectionTable::FromWebDatabase(db)->RemoveExpiredData();
diff --git a/chromium/components/payments/content/payment_manifest_web_data_service.h b/chromium/components/payments/content/payment_manifest_web_data_service.h
index e81845e9cb3..91ef2a4255c 100644
--- a/chromium/components/payments/content/payment_manifest_web_data_service.h
+++ b/chromium/components/payments/content/payment_manifest_web_data_service.h
@@ -5,6 +5,8 @@
#ifndef COMPONENTS_PAYMENTS_CONTENT_PAYMENT_MANIFEST_WEB_DATA_SERVICE_H_
#define COMPONENTS_PAYMENTS_CONTENT_PAYMENT_MANIFEST_WEB_DATA_SERVICE_H_
+#include <stdint.h>
+
#include <memory>
#include <vector>
@@ -23,6 +25,8 @@ class SingleThreadTaskRunner;
namespace payments {
+struct SecurePaymentConfirmationInstrument;
+
// Web data service to read/write data in WebAppManifestSectionTable and
// PaymentMethodManifestTable.
class PaymentManifestWebDataService : public WebDataServiceBase {
@@ -31,23 +35,40 @@ class PaymentManifestWebDataService : public WebDataServiceBase {
scoped_refptr<WebDatabaseService> wdbs,
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner);
- // Adds the web app |manifest|.
+ // Adds the web app `manifest`.
void AddPaymentWebAppManifest(std::vector<WebAppManifestSection> manifest);
- // Adds the |payment_method|'s manifest.
+ // Adds the `payment_method`'s manifest.
void AddPaymentMethodManifest(const std::string& payment_method,
std::vector<std::string> app_package_names);
- // Gets the |web_app|'s manifest.
+ // Gets the `web_app`'s manifest and returns it to the `consumer`, which must
+ // outlive the DB operation, because DB tasks cannot be cancelled.
WebDataServiceBase::Handle GetPaymentWebAppManifest(
const std::string& web_app,
WebDataServiceConsumer* consumer);
- // Gets the |payment_method|'s manifest.
+ // Gets the `payment_method`'s manifest and returns it the `consumer`, which
+ // must outlive the DB operation, because DB tasks cannot be cancelled.
WebDataServiceBase::Handle GetPaymentMethodManifest(
const std::string& payment_method,
WebDataServiceConsumer* consumer);
+ // Adds the secure payment confirmation `instrument` and returns a boolean
+ // status to the `consumer`, which must outlive the DB operation, because DB
+ // tasks cannot be cancelled. The `instrument` should not be null.
+ WebDataServiceBase::Handle AddSecurePaymentConfirmationInstrument(
+ std::unique_ptr<SecurePaymentConfirmationInstrument> instrument,
+ WebDataServiceConsumer* consumer);
+
+ // Gets the secure payment confirmation instrument information for the given
+ // `credential_ids` and returns it to the `consumer`, which must outlive the
+ // DB operation, because DB tasks cannot be cancelled. Please use
+ // `std::move()` for `credential_ids` parameter to avoid extra copies.
+ WebDataServiceBase::Handle GetSecurePaymentConfirmationInstruments(
+ std::vector<std::vector<uint8_t>> credential_ids,
+ WebDataServiceConsumer* consumer);
+
private:
~PaymentManifestWebDataService() override;
@@ -60,6 +81,9 @@ class PaymentManifestWebDataService : public WebDataServiceBase {
const std::string& payment_method,
const std::vector<std::string>& app_package_names,
WebDatabase* db);
+ std::unique_ptr<WDTypedResult> AddSecurePaymentConfirmationInstrumentImpl(
+ std::unique_ptr<SecurePaymentConfirmationInstrument> instrument,
+ WebDatabase* db);
std::unique_ptr<WDTypedResult> GetPaymentWebAppManifestImpl(
const std::string& web_app,
@@ -67,6 +91,9 @@ class PaymentManifestWebDataService : public WebDataServiceBase {
std::unique_ptr<WDTypedResult> GetPaymentMethodManifestImpl(
const std::string& payment_method,
WebDatabase* db);
+ std::unique_ptr<WDTypedResult> GetSecurePaymentConfirmationInstrumentsImpl(
+ std::vector<std::vector<uint8_t>> credential_ids,
+ WebDatabase* db);
DISALLOW_COPY_AND_ASSIGN(PaymentManifestWebDataService);
};
diff --git a/chromium/components/payments/content/payment_method_manifest_table.cc b/chromium/components/payments/content/payment_method_manifest_table.cc
index cc0cb0f1f9a..03e9ce69fd6 100644
--- a/chromium/components/payments/content/payment_method_manifest_table.cc
+++ b/chromium/components/payments/content/payment_method_manifest_table.cc
@@ -8,6 +8,7 @@
#include "base/notreached.h"
#include "base/time/time.h"
+#include "components/payments/core/secure_payment_confirmation_instrument.h"
#include "components/webdata/common/web_database.h"
#include "sql/statement.h"
#include "sql/transaction.h"
@@ -27,9 +28,9 @@ WebDatabaseTable::TypeKey GetPaymentMethodManifestKey() {
} // namespace
-PaymentMethodManifestTable::PaymentMethodManifestTable() {}
+PaymentMethodManifestTable::PaymentMethodManifestTable() = default;
-PaymentMethodManifestTable::~PaymentMethodManifestTable() {}
+PaymentMethodManifestTable::~PaymentMethodManifestTable() = default;
PaymentMethodManifestTable* PaymentMethodManifestTable::FromWebDatabase(
WebDatabase* db) {
@@ -45,7 +46,20 @@ bool PaymentMethodManifestTable::CreateTablesIfNecessary() {
if (!db_->Execute("CREATE TABLE IF NOT EXISTS payment_method_manifest ( "
"expire_date INTEGER NOT NULL DEFAULT 0, "
"method_name VARCHAR, "
- "web_app_id VARCHAR) ")) {
+ "web_app_id VARCHAR)")) {
+ NOTREACHED();
+ return false;
+ }
+
+ // The `credential_id` column is 20 bytes for UbiKey on Linux, but the size
+ // can vary for different authenticators. The relatively small sizes make it
+ // OK to make `credential_id` the primary key.
+ if (!db_->Execute(
+ "CREATE TABLE IF NOT EXISTS secure_payment_confirmation_instrument ( "
+ "credential_id BLOB NOT NULL PRIMARY KEY, "
+ "relying_party_id VARCHAR NOT NULL, "
+ "label VARCHAR NOT NULL, "
+ "icon BLOB NOT NULL)")) {
NOTREACHED();
return false;
}
@@ -66,7 +80,7 @@ bool PaymentMethodManifestTable::MigrateToVersion(
void PaymentMethodManifestTable::RemoveExpiredData() {
const time_t now_date_in_seconds = base::Time::NowFromSystemTime().ToTimeT();
sql::Statement s(db_->GetUniqueStatement(
- "DELETE FROM payment_method_manifest WHERE expire_date < ? "));
+ "DELETE FROM payment_method_manifest WHERE expire_date < ?"));
s.BindInt64(0, now_date_in_seconds);
s.Run();
}
@@ -79,7 +93,7 @@ bool PaymentMethodManifestTable::AddManifest(
return false;
sql::Statement s1(db_->GetUniqueStatement(
- "DELETE FROM payment_method_manifest WHERE method_name=? "));
+ "DELETE FROM payment_method_manifest WHERE method_name=?"));
s1.BindString(0, payment_method);
if (!s1.Run())
return false;
@@ -87,7 +101,7 @@ bool PaymentMethodManifestTable::AddManifest(
sql::Statement s2(
db_->GetUniqueStatement("INSERT INTO payment_method_manifest "
"(expire_date, method_name, web_app_id) "
- "VALUES (?, ?, ?) "));
+ "VALUES (?, ?, ?)"));
const time_t expire_date_in_seconds =
base::Time::NowFromSystemTime().ToTimeT() +
PAYMENT_METHOD_MANIFEST_VALID_TIME_IN_SECONDS;
@@ -123,4 +137,112 @@ std::vector<std::string> PaymentMethodManifestTable::GetManifest(
return web_app_ids;
}
+bool PaymentMethodManifestTable::AddSecurePaymentConfirmationInstrument(
+ const SecurePaymentConfirmationInstrument& instrument) {
+ if (!instrument.IsValid())
+ return false;
+
+ sql::Transaction transaction(db_);
+ if (!transaction.Begin())
+ return false;
+
+ {
+ // Check for credential identifier reuse by a different relying party.
+ sql::Statement s0(
+ db_->GetUniqueStatement("SELECT label "
+ "FROM secure_payment_confirmation_instrument "
+ "WHERE credential_id=? "
+ "AND relying_party_id<>?"));
+ int index = 0;
+ if (!s0.BindBlob(index++, instrument.credential_id.data(),
+ instrument.credential_id.size()))
+ return false;
+
+ if (!s0.BindString(index++, instrument.relying_party_id))
+ return false;
+
+ if (s0.Step())
+ return false;
+ }
+
+ {
+ sql::Statement s1(db_->GetUniqueStatement(
+ "DELETE FROM secure_payment_confirmation_instrument "
+ "WHERE credential_id=?"));
+ if (!s1.BindBlob(0, instrument.credential_id.data(),
+ instrument.credential_id.size()))
+ return false;
+
+ if (!s1.Run())
+ return false;
+ }
+
+ {
+ sql::Statement s2(db_->GetUniqueStatement(
+ "INSERT INTO secure_payment_confirmation_instrument "
+ "(credential_id, relying_party_id, label, icon) "
+ "VALUES (?, ?, ?, ?)"));
+ int index = 0;
+ if (!s2.BindBlob(index++, instrument.credential_id.data(),
+ instrument.credential_id.size()))
+ return false;
+
+ if (!s2.BindString(index++, instrument.relying_party_id))
+ return false;
+
+ if (!s2.BindString16(index++, instrument.label))
+ return false;
+
+ if (!s2.BindBlob(index++, instrument.icon.data(), instrument.icon.size()))
+ return false;
+
+ if (!s2.Run())
+ return false;
+ }
+
+ if (!transaction.Commit())
+ return false;
+
+ return true;
+}
+
+std::vector<std::unique_ptr<SecurePaymentConfirmationInstrument>>
+PaymentMethodManifestTable::GetSecurePaymentConfirmationInstruments(
+ std::vector<std::vector<uint8_t>> credential_ids) {
+ std::vector<std::unique_ptr<SecurePaymentConfirmationInstrument>> instruments;
+ sql::Statement s(
+ db_->GetUniqueStatement("SELECT relying_party_id, label, icon "
+ "FROM secure_payment_confirmation_instrument "
+ "WHERE credential_id=?"));
+ // The `credential_id` temporary variable is not `const` because of the
+ // `std::move()` on line 231.
+ for (auto& credential_id : credential_ids) {
+ s.Reset(true);
+ if (credential_id.empty())
+ continue;
+
+ if (!s.BindBlob(0, credential_id.data(), credential_id.size()))
+ continue;
+
+ if (!s.Step())
+ continue;
+
+ auto instrument = std::make_unique<SecurePaymentConfirmationInstrument>();
+ instrument->credential_id = std::move(credential_id);
+
+ int index = 0;
+ instrument->relying_party_id = s.ColumnString(index++);
+ instrument->label = s.ColumnString16(index++);
+ if (!s.ColumnBlobAsVector(index++, &instrument->icon))
+ continue;
+
+ if (!instrument->IsValid())
+ continue;
+
+ instruments.push_back(std::move(instrument));
+ }
+
+ return instruments;
+}
+
} // namespace payments
diff --git a/chromium/components/payments/content/payment_method_manifest_table.h b/chromium/components/payments/content/payment_method_manifest_table.h
index c994345495d..f2eadb73124 100644
--- a/chromium/components/payments/content/payment_method_manifest_table.h
+++ b/chromium/components/payments/content/payment_method_manifest_table.h
@@ -5,36 +5,51 @@
#ifndef COMPONENTS_PAYMENTS_CONTENT_PAYMENT_METHOD_MANIFEST_TABLE_H_
#define COMPONENTS_PAYMENTS_CONTENT_PAYMENT_METHOD_MANIFEST_TABLE_H_
+#include <memory>
#include <string>
#include <vector>
-#include "base/macros.h"
+#include "base/strings/string16.h"
#include "components/webdata/common/web_database_table.h"
class WebDatabase;
namespace payments {
-// This class manages payment_method_manifest table in SQLite database. It
-// expects the following schema.
+struct SecurePaymentConfirmationInstrument;
+
+// This class manages Web Payment tables in SQLite database. It expects the
+// following schema.
//
-// payment_method_manifest The table stores WebAppManifestSection.id of the
+// payment_method_manifest This table stores WebAppManifestSection.id of the
// supported web app in this payment method manifest.
// Note that a payment method manifest might contain
// multiple supported web apps ids.
//
-// expire_date The expire date in seconds from 1601-01-01 00:00:00
+// expire_date The expire date in seconds from 1601-01-01 00:00:00
// UTC.
-// method_name The method name.
-// web_app_id The supported web app id.
+// method_name The method name.
+// web_app_id The supported web app id.
// (WebAppManifestSection.id).
//
+// secure_payment_confirmation_instrument
+// This table stores instrument information for secure
+// payment confirmation method.
+//
+// credential_id The WebAuthn credential identifier blob. Primary key.
+// relying_party_id The relying party identifier string.
+// label The instrument human-readable label string.
+// icon The serialized SkBitmap blob.
class PaymentMethodManifestTable : public WebDatabaseTable {
public:
PaymentMethodManifestTable();
~PaymentMethodManifestTable() override;
- // Retrieves the PaymentMethodManifestTable* owned by |db|.
+ PaymentMethodManifestTable(const PaymentMethodManifestTable& other) = delete;
+ PaymentMethodManifestTable& operator=(
+ const PaymentMethodManifestTable& other) = delete;
+
+ // Retrieves the PaymentMethodManifestTable* owned by `db`.
static PaymentMethodManifestTable* FromWebDatabase(WebDatabase* db);
// WebDatabaseTable:
@@ -46,17 +61,39 @@ class PaymentMethodManifestTable : public WebDatabaseTable {
// Remove expired data.
void RemoveExpiredData();
- // Adds |payment_method|'s manifest. |web_app_ids| contains supported web apps
+ // Adds `payment_method`'s manifest. `web_app_ids` contains supported web apps
// ids.
bool AddManifest(const std::string& payment_method,
const std::vector<std::string>& web_app_ids);
- // Gets manifest for |payment_method|. Return empty vector if no manifest
+ // Gets manifest for `payment_method`. Return empty vector if no manifest
// exists for this method.
std::vector<std::string> GetManifest(const std::string& payment_method);
- private:
- DISALLOW_COPY_AND_ASSIGN(PaymentMethodManifestTable);
+ // Adds a secure payment confirmation `instrument`. All existing data for the
+ // instrument's (relying_party_id, credential_id) tuple is erased before the
+ // new data is added.
+ //
+ // Each field in the `instrument` should be non-empty and `relying_party_id`
+ // field should be a valid domain string. See:
+ // https://url.spec.whatwg.org/#valid-domain-string
+ //
+ // Returns false for invalid data, e.g., credential reuse between relying
+ // parties, or on failure.
+ bool AddSecurePaymentConfirmationInstrument(
+ const SecurePaymentConfirmationInstrument& instrument);
+
+ // Gets the list of secure payment confirmation instruments for the given list
+ // of `credential_ids`.
+ //
+ // Returns an empty vector when no data is found or when a read error occurs.
+ // Does not return invalid instruments.
+ //
+ // Please use `std::move()` for `credential_ids` parameter to avoid extra
+ // copies.
+ std::vector<std::unique_ptr<SecurePaymentConfirmationInstrument>>
+ GetSecurePaymentConfirmationInstruments(
+ std::vector<std::vector<uint8_t>> credential_ids);
};
} // namespace payments
diff --git a/chromium/components/payments/content/payment_method_manifest_table_unittest.cc b/chromium/components/payments/content/payment_method_manifest_table_unittest.cc
index a3d2c1a1907..199c6c07e48 100644
--- a/chromium/components/payments/content/payment_method_manifest_table_unittest.cc
+++ b/chromium/components/payments/content/payment_method_manifest_table_unittest.cc
@@ -4,21 +4,77 @@
#include "components/payments/content/payment_method_manifest_table.h"
+#include <stdint.h>
+
#include <memory>
+#include <utility>
+#include <vector>
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/stl_util.h"
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/payments/core/secure_payment_confirmation_instrument.h"
#include "components/webdata/common/web_database.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace payments {
namespace {
+// Creates an icon for testing.
+std::vector<uint8_t> CreateIcon(uint8_t first_byte = 0) {
+ std::vector<uint8_t> icon;
+ icon.push_back(first_byte++);
+ icon.push_back(first_byte++);
+ icon.push_back(first_byte++);
+ icon.push_back(first_byte);
+ return icon;
+}
+
+// Creates one credential identifier for testing.
+std::vector<uint8_t> CreateCredentialId(uint8_t first_byte = 0) {
+ std::vector<uint8_t> credential_id;
+ credential_id.push_back(first_byte++);
+ credential_id.push_back(first_byte++);
+ credential_id.push_back(first_byte++);
+ credential_id.push_back(first_byte);
+ return credential_id;
+}
+
+// Creates a list of one credential identifier for testing.
+std::vector<std::vector<uint8_t>> CreateCredentialIdList(
+ uint8_t first_byte = 0) {
+ std::vector<std::vector<uint8_t>> credential_ids;
+ credential_ids.push_back(CreateCredentialId(first_byte));
+ return credential_ids;
+}
+
+void ExpectOneValidInstrument(
+ const std::vector<uint8_t>& credential_id,
+ const std::string& relying_party_id,
+ const std::string& label,
+ const std::vector<uint8_t>& icon,
+ std::vector<std::unique_ptr<SecurePaymentConfirmationInstrument>>
+ instruments) {
+ ASSERT_EQ(1U, instruments.size());
+ ASSERT_NE(nullptr, instruments.back());
+ ASSERT_TRUE(instruments.back()->IsValid());
+ EXPECT_EQ(credential_id, instruments.back()->credential_id);
+ EXPECT_EQ(relying_party_id, instruments.back()->relying_party_id);
+ EXPECT_EQ(base::ASCIIToUTF16(label), instruments.back()->label);
+ EXPECT_EQ(icon, instruments.back()->icon);
+}
+
class PaymentMethodManifestTableTest : public testing::Test {
public:
- PaymentMethodManifestTableTest() {}
- ~PaymentMethodManifestTableTest() override {}
+ PaymentMethodManifestTableTest() = default;
+ ~PaymentMethodManifestTableTest() override = default;
+
+ PaymentMethodManifestTableTest(const PaymentMethodManifestTableTest& other) =
+ delete;
+ PaymentMethodManifestTableTest& operator=(
+ const PaymentMethodManifestTableTest& other) = delete;
protected:
void SetUp() override {
@@ -31,15 +87,10 @@ class PaymentMethodManifestTableTest : public testing::Test {
ASSERT_EQ(sql::INIT_OK, db_->Init(file_));
}
- void TearDown() override {}
-
base::FilePath file_;
base::ScopedTempDir temp_dir_;
std::unique_ptr<PaymentMethodManifestTable> table_;
std::unique_ptr<WebDatabase> db_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(PaymentMethodManifestTableTest);
};
TEST_F(PaymentMethodManifestTableTest, GetNonExistManifest) {
@@ -116,5 +167,175 @@ TEST_F(PaymentMethodManifestTableTest, AddAndGetMultipleManifest) {
ASSERT_TRUE(base::Contains(alicepay_web_app_ids, web_app_ids[1]));
}
+TEST_F(PaymentMethodManifestTableTest, GetNonExistingInstrument) {
+ PaymentMethodManifestTable* table =
+ PaymentMethodManifestTable::FromWebDatabase(db_.get());
+ EXPECT_TRUE(
+ table->GetSecurePaymentConfirmationInstruments(CreateCredentialIdList())
+ .empty());
+
+ EXPECT_TRUE(table->GetSecurePaymentConfirmationInstruments({}).empty());
+
+ EXPECT_TRUE(
+ table->GetSecurePaymentConfirmationInstruments({std::vector<uint8_t>()})
+ .empty());
+}
+
+TEST_F(PaymentMethodManifestTableTest, AddAndGetOneValidInstrument) {
+ PaymentMethodManifestTable* table =
+ PaymentMethodManifestTable::FromWebDatabase(db_.get());
+
+ EXPECT_TRUE(table->AddSecurePaymentConfirmationInstrument(
+ SecurePaymentConfirmationInstrument(
+ CreateCredentialId(), "relying-party.example",
+ base::ASCIIToUTF16("Instrument label"), CreateIcon())));
+
+ auto instruments =
+ table->GetSecurePaymentConfirmationInstruments(CreateCredentialIdList());
+
+ ExpectOneValidInstrument({0, 1, 2, 3}, "relying-party.example",
+ "Instrument label", {0, 1, 2, 3},
+ std::move(instruments));
+
+ EXPECT_TRUE(table->GetSecurePaymentConfirmationInstruments({}).empty());
+ EXPECT_TRUE(
+ table->GetSecurePaymentConfirmationInstruments({std::vector<uint8_t>()})
+ .empty());
+}
+
+TEST_F(PaymentMethodManifestTableTest, AddingInvalidInstrumentReturnsFalse) {
+ PaymentMethodManifestTable* table =
+ PaymentMethodManifestTable::FromWebDatabase(db_.get());
+
+ // An empty credential.
+ EXPECT_FALSE(table->AddSecurePaymentConfirmationInstrument(
+ SecurePaymentConfirmationInstrument(
+ /*credential_id=*/{}, "relying-party.example",
+ base::ASCIIToUTF16("Instrument label"), CreateIcon())));
+
+ // Empty relying party identifier.
+ EXPECT_FALSE(table->AddSecurePaymentConfirmationInstrument(
+ SecurePaymentConfirmationInstrument(
+ CreateCredentialId(), /*relying_party_id=*/"",
+ base::ASCIIToUTF16("Instrument label"), CreateIcon())));
+
+ // Empty label.
+ EXPECT_FALSE(table->AddSecurePaymentConfirmationInstrument(
+ SecurePaymentConfirmationInstrument(
+ CreateCredentialId(), "relying-party.example",
+ /*label=*/base::string16(), CreateIcon())));
+
+ // Empty icon.
+ EXPECT_FALSE(table->AddSecurePaymentConfirmationInstrument(
+ SecurePaymentConfirmationInstrument(
+ CreateCredentialId(), "relying-party.example",
+ base::ASCIIToUTF16("Instrument label"), {})));
+}
+
+TEST_F(PaymentMethodManifestTableTest, UpdatingInstrumentReturnsTrue) {
+ PaymentMethodManifestTable* table =
+ PaymentMethodManifestTable::FromWebDatabase(db_.get());
+ EXPECT_TRUE(table->AddSecurePaymentConfirmationInstrument(
+ SecurePaymentConfirmationInstrument(
+ CreateCredentialId(/*first_byte=*/0), "relying-party.example",
+ base::ASCIIToUTF16("Instrument label 1"),
+ CreateIcon(/*first_byte=*/0))));
+
+ EXPECT_TRUE(table->AddSecurePaymentConfirmationInstrument(
+ SecurePaymentConfirmationInstrument(
+ CreateCredentialId(/*first_byte=*/0), "relying-party.example",
+ base::ASCIIToUTF16("Instrument label 2"),
+ CreateIcon(/*first_byte=*/4))));
+
+ auto instruments = table->GetSecurePaymentConfirmationInstruments(
+ CreateCredentialIdList(/*first_byte=*/0));
+ ExpectOneValidInstrument({0, 1, 2, 3}, "relying-party.example",
+ "Instrument label 2", {4, 5, 6, 7},
+ std::move(instruments));
+}
+
+TEST_F(PaymentMethodManifestTableTest,
+ DifferentRelyingPartiesCannotUseSameCredentialIdentifier) {
+ PaymentMethodManifestTable* table =
+ PaymentMethodManifestTable::FromWebDatabase(db_.get());
+ EXPECT_TRUE(table->AddSecurePaymentConfirmationInstrument(
+ SecurePaymentConfirmationInstrument(
+ CreateCredentialId(/*first_byte=*/0), "relying-party-1.example",
+ base::ASCIIToUTF16("Instrument label 1"),
+ CreateIcon(/*first_byte=*/0))));
+
+ EXPECT_FALSE(table->AddSecurePaymentConfirmationInstrument(
+ SecurePaymentConfirmationInstrument(
+ CreateCredentialId(/*first_byte=*/0), "relying-party-2.example",
+ base::ASCIIToUTF16("Instrument label 2"),
+ CreateIcon(/*first_byte=*/4))));
+
+ auto instruments = table->GetSecurePaymentConfirmationInstruments(
+ CreateCredentialIdList(/*first_byte=*/0));
+ ExpectOneValidInstrument({0, 1, 2, 3}, "relying-party-1.example",
+ "Instrument label 1", {0, 1, 2, 3},
+ std::move(instruments));
+}
+
+TEST_F(PaymentMethodManifestTableTest, RelyingPartyCanHaveMultipleCredentials) {
+ PaymentMethodManifestTable* table =
+ PaymentMethodManifestTable::FromWebDatabase(db_.get());
+
+ EXPECT_TRUE(table->AddSecurePaymentConfirmationInstrument(
+ SecurePaymentConfirmationInstrument(
+ CreateCredentialId(/*first_byte=*/0), "relying-party.example",
+ base::ASCIIToUTF16("Instrument label 1"),
+ CreateIcon(/*first_byte=*/0))));
+
+ EXPECT_TRUE(table->AddSecurePaymentConfirmationInstrument(
+ SecurePaymentConfirmationInstrument(
+ CreateCredentialId(/*first_byte=*/4), "relying-party.example",
+ base::ASCIIToUTF16("Instrument label 2"),
+ CreateIcon(/*first_byte=*/4))));
+
+ auto instruments = table->GetSecurePaymentConfirmationInstruments(
+ CreateCredentialIdList(/*first_byte=*/0));
+
+ ExpectOneValidInstrument({0, 1, 2, 3}, "relying-party.example",
+ "Instrument label 1", {0, 1, 2, 3},
+ std::move(instruments));
+
+ instruments = table->GetSecurePaymentConfirmationInstruments(
+ CreateCredentialIdList(/*first_byte=*/4));
+
+ ExpectOneValidInstrument({4, 5, 6, 7}, "relying-party.example",
+ "Instrument label 2", {4, 5, 6, 7},
+ std::move(instruments));
+
+ std::vector<std::vector<uint8_t>> credential_ids;
+ credential_ids.push_back(CreateCredentialId(/*first_byte=*/0));
+ credential_ids.push_back(CreateCredentialId(/*first_byte=*/4));
+
+ instruments =
+ table->GetSecurePaymentConfirmationInstruments(std::move(credential_ids));
+
+ ASSERT_EQ(2U, instruments.size());
+
+ ASSERT_NE(nullptr, instruments.front());
+ ASSERT_TRUE(instruments.front()->IsValid());
+ std::vector<uint8_t> expected_credential_id = {0, 1, 2, 3};
+ EXPECT_EQ(expected_credential_id, instruments.front()->credential_id);
+ EXPECT_EQ("relying-party.example", instruments.front()->relying_party_id);
+ EXPECT_EQ(base::ASCIIToUTF16("Instrument label 1"),
+ instruments.front()->label);
+ std::vector<uint8_t> expected_icon = {0, 1, 2, 3};
+ EXPECT_EQ(expected_icon, instruments.front()->icon);
+
+ ASSERT_NE(nullptr, instruments.back());
+ ASSERT_TRUE(instruments.back()->IsValid());
+ expected_credential_id = {4, 5, 6, 7};
+ EXPECT_EQ(expected_credential_id, instruments.back()->credential_id);
+ EXPECT_EQ("relying-party.example", instruments.back()->relying_party_id);
+ EXPECT_EQ(base::ASCIIToUTF16("Instrument label 2"),
+ instruments.back()->label);
+ expected_icon = {4, 5, 6, 7};
+ EXPECT_EQ(expected_icon, instruments.back()->icon);
+}
+
} // namespace
} // namespace payments
diff --git a/chromium/components/payments/content/payment_request.cc b/chromium/components/payments/content/payment_request.cc
index 34323322dc5..a34ae79f92c 100644
--- a/chromium/components/payments/content/payment_request.cc
+++ b/chromium/components/payments/content/payment_request.cc
@@ -27,6 +27,7 @@
#include "components/payments/core/payment_details.h"
#include "components/payments/core/payment_details_validation.h"
#include "components/payments/core/payment_prefs.h"
+#include "components/payments/core/payment_request_delegate.h"
#include "components/payments/core/payments_experimental_features.h"
#include "components/payments/core/payments_validators.h"
#include "components/payments/core/url_util.h"
@@ -123,6 +124,8 @@ void PaymentRequest::Init(
return;
}
+ journey_logger_.RecordCheckoutStep(
+ JourneyLogger::CheckoutFunnelStep::kInitiated);
is_initialized_ = true;
client_.Bind(std::move(client));
@@ -208,10 +211,17 @@ void PaymentRequest::Init(
base::Contains(spec_->url_payment_method_identifiers(), google_pay_url) ||
base::Contains(spec_->url_payment_method_identifiers(),
android_pay_url),
+ /*requested_method_secure_payment_confirmation=*/
+ spec_->IsSecurePaymentConfirmationRequested(),
/*requested_method_other=*/non_google_it !=
spec_->url_payment_method_identifiers().end());
payment_handler_host_.set_payment_request_id_for_logs(*spec_->details().id);
+
+ if (spec_->IsSecurePaymentConfirmationRequested()) {
+ delegate_->set_dialog_type(
+ PaymentRequestDelegate::DialogType::SECURE_PAYMENT_CONFIRMATION);
+ }
}
void PaymentRequest::Show(bool is_user_gesture, bool wait_for_updated_details) {
@@ -227,6 +237,8 @@ void PaymentRequest::Show(bool is_user_gesture, bool wait_for_updated_details) {
return;
}
+ journey_logger_.RecordCheckoutStep(
+ JourneyLogger::CheckoutFunnelStep::kShowCalled);
is_show_called_ = true;
journey_logger_.SetTriggerTime();
@@ -482,7 +494,7 @@ void PaymentRequest::CanMakePayment() {
}
}
-void PaymentRequest::HasEnrolledInstrument(bool per_method_quota) {
+void PaymentRequest::HasEnrolledInstrument() {
if (!IsInitialized()) {
log_.Error(errors::kCannotCallHasEnrolledInstrumentWithoutInit);
OnConnectionTerminated();
@@ -496,12 +508,11 @@ void PaymentRequest::HasEnrolledInstrument(bool per_method_quota) {
if (!delegate_->GetPrefService()->GetBoolean(kCanMakePaymentEnabled) ||
!state_) {
- HasEnrolledInstrumentCallback(per_method_quota,
- /*has_enrolled_instrument=*/false);
+ HasEnrolledInstrumentCallback(/*has_enrolled_instrument=*/false);
} else {
state_->HasEnrolledInstrument(
base::BindOnce(&PaymentRequest::HasEnrolledInstrumentCallback,
- weak_ptr_factory_.GetWeakPtr(), per_method_quota));
+ weak_ptr_factory_.GetWeakPtr()));
}
}
@@ -560,7 +571,7 @@ void PaymentRequest::AreRequestedMethodsSupportedCallback(
bool methods_supported,
const std::string& error_message) {
if (is_show_called_ && observer_for_testing_)
- observer_for_testing_->OnShowAppsReady(weak_ptr_factory_.GetWeakPtr());
+ observer_for_testing_->OnAppListReady(weak_ptr_factory_.GetWeakPtr());
if (methods_supported) {
if (SatisfiesSkipUIConstraints())
@@ -581,6 +592,10 @@ void PaymentRequest::AreRequestedMethodsSupportedCallback(
}
}
+base::WeakPtr<PaymentRequest> PaymentRequest::GetWeakPtr() {
+ return weak_ptr_factory_.GetWeakPtr();
+}
+
bool PaymentRequest::IsInitialized() const {
return is_initialized_ && client_ && client_.is_bound() &&
receiver_.is_bound();
@@ -619,8 +634,9 @@ bool PaymentRequest::OnlySingleAppCanProvideAllRequiredInformation() const {
}
bool PaymentRequest::SatisfiesSkipUIConstraints() {
- // Only allowing URL base payment apps to skip the payment sheet.
+ // Only allowing URL based payment apps to skip the payment sheet.
skipped_payment_request_ui_ =
+ !spec()->IsSecurePaymentConfirmationRequested() &&
(spec()->url_payment_method_identifiers().size() > 0 ||
delegate_->SkipUiForBasicCard()) &&
base::FeatureList::IsEnabled(features::kWebPaymentsSingleAppUiSkip) &&
@@ -657,17 +673,22 @@ void PaymentRequest::OnPaymentResponseAvailable(
case PaymentApp::Type::AUTOFILL:
selected_event = JourneyLogger::Event::EVENT_SELECTED_CREDIT_CARD;
break;
- case PaymentApp::Type::SERVICE_WORKER_APP: {
+ case PaymentApp::Type::SERVICE_WORKER_APP:
+ // Intentionally fall through.
+ case PaymentApp::Type::NATIVE_MOBILE_APP: {
selected_event = IsGooglePaymentMethod(response->method_name)
? JourneyLogger::Event::EVENT_SELECTED_GOOGLE
: JourneyLogger::Event::EVENT_SELECTED_OTHER;
break;
}
+ case PaymentApp::Type::INTERNAL: {
+ if (response->method_name == methods::kSecurePaymentConfirmation) {
+ selected_event =
+ JourneyLogger::Event::EVENT_SELECTED_SECURE_PAYMENT_CONFIRMATION;
+ }
+ break;
+ }
case PaymentApp::Type::UNDEFINED:
- // Intentionally fall through.
- case PaymentApp::Type::NATIVE_MOBILE_APP:
- // Intentionally fall through.
- case PaymentApp::Type::INTERNAL:
NOTREACHED();
break;
}
@@ -738,6 +759,21 @@ void PaymentRequest::DidStartMainFrameNavigationToDifferentDocument(
: JourneyLogger::ABORT_REASON_MERCHANT_NAVIGATION);
}
+void PaymentRequest::RenderFrameDeleted(
+ content::RenderFrameHost* render_frame_host) {
+ DCHECK(render_frame_host ==
+ content::RenderFrameHost::FromID(initiator_frame_routing_id_));
+ // RenderFrameHost is usually deleted explicitly before PaymentRequest
+ // destruction if the user closes the tab or browser window without closing
+ // the payment request dialog.
+ RecordFirstAbortReason(JourneyLogger::ABORT_REASON_ABORTED_BY_USER);
+ // But don't bother sending errors to |client_| because the mojo pipe will be
+ // torn down anyways when RenderFrameHost is destroyed. It's not safe to call
+ // UserCancelled() here because it is not re-entrant.
+ // TODO(crbug.com/1121841) Make UserCancelled re-entrant.
+ OnConnectionTerminated();
+}
+
void PaymentRequest::OnConnectionTerminated() {
// We are here because of a browser-side error, or likely as a result of the
// disconnect_handler on |receiver_|, which can mean that the renderer
@@ -757,6 +793,8 @@ void PaymentRequest::OnConnectionTerminated() {
void PaymentRequest::Pay() {
journey_logger_.SetEventOccurred(JourneyLogger::EVENT_PAY_CLICKED);
+ journey_logger_.RecordCheckoutStep(
+ JourneyLogger::CheckoutFunnelStep::kPaymentHandlerInvoked);
DCHECK(state_->selected_app());
state_->selected_app()->SetPaymentHandlerHost(
payment_handler_host_.AsWeakPtr());
@@ -799,12 +837,11 @@ void PaymentRequest::CanMakePaymentCallback(bool can_make_payment) {
}
void PaymentRequest::HasEnrolledInstrumentCallback(
- bool per_method_quota,
bool has_enrolled_instrument) {
if (!spec_ || CanMakePaymentQueryFactory::GetInstance()
->GetForContext(web_contents_->GetBrowserContext())
->CanQuery(top_level_origin_, frame_origin_,
- spec_->query_for_quota(), per_method_quota)) {
+ spec_->query_for_quota())) {
RespondToHasEnrolledInstrumentQuery(has_enrolled_instrument,
/*warn_local_development=*/false);
} else if (UrlUtil::IsLocalDevelopmentUrl(frame_origin_)) {
diff --git a/chromium/components/payments/content/payment_request.h b/chromium/components/payments/content/payment_request.h
index 24c759882ba..da18347c5aa 100644
--- a/chromium/components/payments/content/payment_request.h
+++ b/chromium/components/payments/content/payment_request.h
@@ -51,8 +51,8 @@ class PaymentRequest : public mojom::PaymentRequest,
virtual void OnCanMakePaymentReturned() = 0;
virtual void OnHasEnrolledInstrumentCalled() = 0;
virtual void OnHasEnrolledInstrumentReturned() = 0;
- virtual void OnShowAppsReady(
- base::WeakPtr<PaymentRequest> payment_request) {}
+ virtual void OnAppListReady(base::WeakPtr<PaymentRequest> payment_request) {
+ }
virtual void OnNotSupportedError() = 0;
virtual void OnConnectionTerminated() = 0;
virtual void OnAbortCalled() = 0;
@@ -83,7 +83,7 @@ class PaymentRequest : public mojom::PaymentRequest,
void Abort() override;
void Complete(mojom::PaymentComplete result) override;
void CanMakePayment() override;
- void HasEnrolledInstrument(bool per_method_quota) override;
+ void HasEnrolledInstrument() override;
// PaymentHandlerHost::Delegate
bool ChangePaymentMethod(const std::string& method_name,
@@ -111,6 +111,12 @@ class PaymentRequest : public mojom::PaymentRequest,
// another document, but before the PaymentRequest is destroyed.
void DidStartMainFrameNavigationToDifferentDocument(bool is_user_initiated);
+ // Called when the frame attached to this PaymentRequest is about to be
+ // destroyed. This is used to clean up before the RenderFrameHost is actually
+ // destroyed because some objects held by the PaymentRequest (e.g.
+ // InternalAuthenticator) must be out-lived by the RenderFrameHost.
+ void RenderFrameDeleted(content::RenderFrameHost* render_frame_host);
+
// As a result of a browser-side error or renderer-initiated mojo channel
// closure (e.g. there was an error on the renderer side, or payment was
// successful), this method is called. It is responsible for cleaning up,
@@ -130,6 +136,10 @@ class PaymentRequest : public mojom::PaymentRequest,
content::WebContents* web_contents() { return web_contents_; }
+ const content::GlobalFrameRoutingId& initiator_frame_routing_id() {
+ return initiator_frame_routing_id_;
+ }
+
bool skipped_payment_request_ui() { return skipped_payment_request_ui_; }
bool is_show_user_gesture() const { return is_show_user_gesture_; }
@@ -139,6 +149,8 @@ class PaymentRequest : public mojom::PaymentRequest,
PaymentRequestSpec* spec() const { return spec_.get(); }
PaymentRequestState* state() const { return state_.get(); }
+ base::WeakPtr<PaymentRequest> GetWeakPtr();
+
private:
// Returns true after init() has been called and the mojo connection has been
// established. If the mojo connection gets later disconnected, this will
@@ -169,8 +181,7 @@ class PaymentRequest : public mojom::PaymentRequest,
// The callback for PaymentRequestState::HasEnrolledInstrument. Checks for
// query quota and may send QUERY_QUOTA_EXCEEDED.
- void HasEnrolledInstrumentCallback(bool per_method_quota,
- bool has_enrolled_instrument);
+ void HasEnrolledInstrumentCallback(bool has_enrolled_instrument);
// The callback for PaymentRequestState::AreRequestedMethodsSupported.
void AreRequestedMethodsSupportedCallback(bool methods_supported,
diff --git a/chromium/components/payments/content/payment_request_dialog.h b/chromium/components/payments/content/payment_request_dialog.h
index 0f3c186323e..4a6d02bcf5b 100644
--- a/chromium/components/payments/content/payment_request_dialog.h
+++ b/chromium/components/payments/content/payment_request_dialog.h
@@ -47,6 +47,9 @@ class PaymentRequestDialog {
virtual void ShowPaymentHandlerScreen(
const GURL& url,
PaymentHandlerOpenWindowCallback callback) = 0;
+
+ // Confirms payment. Used only in tests.
+ virtual void ConfirmPaymentForTesting() = 0;
};
} // namespace payments
diff --git a/chromium/components/payments/content/payment_request_spec.cc b/chromium/components/payments/content/payment_request_spec.cc
index bbb7c61a19a..984656779bb 100644
--- a/chromium/components/payments/content/payment_request_spec.cc
+++ b/chromium/components/payments/content/payment_request_spec.cc
@@ -358,6 +358,18 @@ PaymentRequestSpec::GetShippingOptions() const {
return *details_->shipping_options;
}
+bool PaymentRequestSpec::IsSecurePaymentConfirmationRequested() const {
+ // No other payment method will be requested together with secure payment
+ // confirmation.
+ return payment_method_identifiers_set_.size() == 1 &&
+ *payment_method_identifiers_set_.begin() ==
+ methods::kSecurePaymentConfirmation;
+}
+
+base::WeakPtr<PaymentRequestSpec> PaymentRequestSpec::GetWeakPtr() {
+ return weak_ptr_factory_.GetWeakPtr();
+}
+
const mojom::PaymentDetailsModifierPtr*
PaymentRequestSpec::GetApplicableModifier(PaymentApp* selected_app) const {
if (!selected_app ||
diff --git a/chromium/components/payments/content/payment_request_spec.h b/chromium/components/payments/content/payment_request_spec.h
index 87af537dadd..caa20936bba 100644
--- a/chromium/components/payments/content/payment_request_spec.h
+++ b/chromium/components/payments/content/payment_request_spec.h
@@ -11,6 +11,7 @@
#include <vector>
#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/strings/string16.h"
#include "components/autofill/core/browser/data_model/credit_card.h"
@@ -119,6 +120,8 @@ class PaymentRequestSpec : public PaymentOptionsProvider,
bool request_payer_email() const override;
PaymentShippingType shipping_type() const override;
+ const mojom::PaymentOptionsPtr& payment_options() const { return options_; }
+
// Returns the query to be used for the quota on hasEnrolledInstrument()
// calls. Generally this returns the payment method identifiers and their
// corresponding data. However, in the case of basic-card with
@@ -200,6 +203,10 @@ class PaymentRequestSpec : public PaymentOptionsProvider,
return method_data_;
}
+ bool IsSecurePaymentConfirmationRequested() const;
+
+ base::WeakPtr<PaymentRequestSpec> GetWeakPtr();
+
private:
// Returns the first applicable modifier in the Payment Request for the
// |selected_app|.
@@ -280,6 +287,8 @@ class PaymentRequestSpec : public PaymentOptionsProvider,
base::string16 retry_error_message_;
mojom::PayerErrorsPtr payer_errors_;
+ base::WeakPtrFactory<PaymentRequestSpec> weak_ptr_factory_{this};
+
DISALLOW_COPY_AND_ASSIGN(PaymentRequestSpec);
};
diff --git a/chromium/components/payments/content/payment_request_state.cc b/chromium/components/payments/content/payment_request_state.cc
index 8b7640c1937..4c5dda70ac2 100644
--- a/chromium/components/payments/content/payment_request_state.cc
+++ b/chromium/components/payments/content/payment_request_state.cc
@@ -73,7 +73,7 @@ PaymentRequestState::PaymentRequestState(
frame_origin_(frame_origin),
frame_security_origin_(frame_security_origin),
app_locale_(app_locale),
- spec_(spec),
+ spec_(spec->GetWeakPtr()),
delegate_(delegate),
personal_data_manager_(personal_data_manager),
journey_logger_(journey_logger),
@@ -108,7 +108,11 @@ void PaymentRequestState::ShowProcessingSpinner() {
}
PaymentRequestSpec* PaymentRequestState::GetSpec() const {
- return spec_;
+ return spec_.get();
+}
+
+std::string PaymentRequestState::GetTwaPackageName() const {
+ return GetPaymentRequestDelegate()->GetTwaPackageName();
}
const GURL& PaymentRequestState::GetTopOrigin() {
@@ -130,9 +134,15 @@ content::RenderFrameHost* PaymentRequestState::GetInitiatorRenderFrameHost()
const std::vector<mojom::PaymentMethodDataPtr>&
PaymentRequestState::GetMethodData() const {
+ DCHECK(GetSpec());
return GetSpec()->method_data();
}
+std::unique_ptr<autofill::InternalAuthenticator>
+PaymentRequestState::CreateInternalAuthenticator() const {
+ return GetPaymentRequestDelegate()->CreateInternalAuthenticator();
+}
+
scoped_refptr<PaymentManifestWebDataService>
PaymentRequestState::GetPaymentManifestWebDataService() const {
return GetPaymentRequestDelegate()->GetPaymentManifestWebDataService();
@@ -150,7 +160,7 @@ bool PaymentRequestState::IsRequestedAutofillDataAvailable() {
bool PaymentRequestState::MayCrawlForInstallablePaymentApps() {
return PaymentsExperimentalFeatures::IsEnabled(
features::kAlwaysAllowJustInTimePaymentApp) ||
- !spec_->supports_basic_card();
+ !spec_ || !spec_->supports_basic_card();
}
bool PaymentRequestState::IsOffTheRecord() const {
@@ -186,6 +196,25 @@ void PaymentRequestState::OnDoneCreatingPaymentApps() {
if (--number_of_payment_app_factories_ > 0U)
return;
+ if (IsInTwa()) {
+ // If a preferred payment app is present (e.g. Play Billing within a TWA),
+ // all other payment apps are ignored.
+ bool has_preferred_app =
+ std::any_of(available_apps_.begin(), available_apps_.end(),
+ [](const auto& app) { return app->IsPreferred(); });
+ if (has_preferred_app) {
+ available_apps_.erase(
+ std::remove_if(available_apps_.begin(), available_apps_.end(),
+ [](const auto& app) { return !app->IsPreferred(); }),
+ available_apps_.end());
+
+ // By design, only one payment app can be preferred.
+ DCHECK_EQ(available_apps_.size(), 1u);
+ if (available_apps_.size() > 1)
+ available_apps_.resize(1);
+ }
+ }
+
SetDefaultProfileSelections();
get_all_apps_finished_ = true;
@@ -198,11 +227,12 @@ void PaymentRequestState::OnDoneCreatingPaymentApps() {
// Fulfill the pending CanMakePayment call.
if (can_make_payment_callback_)
- std::move(can_make_payment_callback_).Run(are_requested_methods_supported_);
+ std::move(can_make_payment_callback_).Run(GetCanMakePaymentValue());
// Fulfill the pending HasEnrolledInstrument call.
if (has_enrolled_instrument_callback_)
- std::move(has_enrolled_instrument_callback_).Run(has_enrolled_instrument_);
+ std::move(has_enrolled_instrument_callback_)
+ .Run(GetHasEnrolledInstrumentValue());
// Fulfill the pending AreRequestedMethodsSupported call.
if (are_requested_methods_supported_callback_)
@@ -210,6 +240,10 @@ void PaymentRequestState::OnDoneCreatingPaymentApps() {
std::move(are_requested_methods_supported_callback_));
}
+void PaymentRequestState::SetCanMakePaymentEvenWithoutApps() {
+ can_make_payment_even_without_apps_ = true;
+}
+
void PaymentRequestState::OnPaymentResponseReady(
mojom::PaymentResponsePtr payment_response) {
delegate_->OnPaymentResponseAvailable(std::move(payment_response));
@@ -221,6 +255,9 @@ void PaymentRequestState::OnPaymentResponseError(
}
void PaymentRequestState::OnSpecUpdated() {
+ if (!spec_)
+ return;
+
autofill::AutofillProfile* selected_shipping_profile =
selected_shipping_profile_;
autofill::AutofillProfile* selected_contact_profile =
@@ -260,7 +297,7 @@ void PaymentRequestState::CanMakePayment(StatusCallback callback) {
return;
}
- PostStatusCallback(std::move(callback), are_requested_methods_supported_);
+ PostStatusCallback(std::move(callback), GetCanMakePaymentValue());
}
void PaymentRequestState::HasEnrolledInstrument(StatusCallback callback) {
@@ -270,7 +307,7 @@ void PaymentRequestState::HasEnrolledInstrument(StatusCallback callback) {
return;
}
- PostStatusCallback(std::move(callback), has_enrolled_instrument_);
+ PostStatusCallback(std::move(callback), GetHasEnrolledInstrumentValue());
}
void PaymentRequestState::AreRequestedMethodsSupported(
@@ -297,6 +334,9 @@ void PaymentRequestState::CheckRequestedMethodsSupported(
MethodsSupportedCallback callback) {
DCHECK(get_all_apps_finished_);
+ if (!spec_)
+ return;
+
// Don't modify the value of |are_requested_methods_supported_|, because it's
// used for canMakePayment().
bool supported = are_requested_methods_supported_;
@@ -310,11 +350,10 @@ void PaymentRequestState::CheckRequestedMethodsSupported(
get_all_payment_apps_error_ = errors::kStrictBasicCardShowReject;
}
- bool is_in_twa = !payment_request_delegate_->GetTwaPackageName().empty();
if (!supported && get_all_payment_apps_error_.empty() &&
base::Contains(spec_->payment_method_identifiers_set(),
methods::kGooglePlayBilling) &&
- !is_in_twa) {
+ !IsInTwa()) {
get_all_payment_apps_error_ = errors::kAppStoreMethodOnlySupportedInTwa;
}
@@ -337,9 +376,12 @@ void PaymentRequestState::RemoveObserver(Observer* observer) {
void PaymentRequestState::GeneratePaymentResponse() {
DCHECK(is_ready_to_pay());
+ if (!spec_)
+ return;
+
// Once the response is ready, will call back into OnPaymentResponseReady.
response_helper_ = std::make_unique<PaymentResponseHelper>(
- app_locale_, spec_, selected_app_, payment_request_delegate_,
+ app_locale_, spec_.get(), selected_app_, payment_request_delegate_,
selected_shipping_profile_, selected_contact_profile_, this);
}
@@ -429,6 +471,9 @@ void PaymentRequestState::AddAutofillContactProfile(
void PaymentRequestState::SetSelectedShippingOption(
const std::string& shipping_option_id) {
+ if (!spec_)
+ return;
+
spec_->StartWaitingForUpdateWith(
PaymentRequestSpec::UpdateReason::SHIPPING_OPTION);
// This will inform the merchant and will lead to them calling updateWith with
@@ -439,6 +484,9 @@ void PaymentRequestState::SetSelectedShippingOption(
void PaymentRequestState::SetSelectedShippingProfile(
autofill::AutofillProfile* profile,
SectionSelectionStatus selection_status) {
+ if (!spec_)
+ return;
+
spec_->StartWaitingForUpdateWith(
PaymentRequestSpec::UpdateReason::SHIPPING_ADDRESS);
selected_shipping_profile_ = profile;
@@ -534,7 +582,8 @@ void PaymentRequestState::SelectDefaultShippingAddressAndNotifyObservers() {
// Only pre-select an address if the merchant provided at least one selected
// shipping option, and the top profile is complete. Assumes that profiles
// have already been sorted for completeness and frecency.
- if (!shipping_profiles().empty() && spec_->selected_shipping_option() &&
+ if (!shipping_profiles().empty() && spec_ &&
+ spec_->selected_shipping_option() &&
profile_comparator()->IsShippingComplete(shipping_profiles_[0])) {
selected_shipping_profile_ = shipping_profiles()[0];
}
@@ -546,13 +595,16 @@ void PaymentRequestState::SelectDefaultShippingAddressAndNotifyObservers() {
}
bool PaymentRequestState::ShouldShowShippingSection() const {
- if (!spec_->request_shipping())
+ if (!spec_ || !spec_->request_shipping())
return false;
return selected_app_ ? !selected_app_->HandlesShippingAddress() : true;
}
bool PaymentRequestState::ShouldShowContactSection() const {
+ if (!spec_)
+ return false;
+
if (spec_->request_payer_name() &&
(!selected_app_ || !selected_app_->HandlesPayerName())) {
return true;
@@ -642,7 +694,7 @@ void PaymentRequestState::SetDefaultProfileSelections() {
// Record the missing required payment fields when no complete payment
// info exists.
if (available_apps_.empty()) {
- if (spec_->supports_basic_card()) {
+ if (spec_ && spec_->supports_basic_card()) {
// All fields are missing when basic-card is requested but no card exits.
base::UmaHistogramSparse("PaymentRequest.MissingPaymentFields",
CREDIT_CARD_EXPIRED | CREDIT_CARD_NO_CARDHOLDER |
@@ -686,7 +738,7 @@ bool PaymentRequestState::ArePaymentDetailsSatisfied() {
}
bool PaymentRequestState::ArePaymentOptionsSatisfied() {
- if (is_waiting_for_merchant_validation_)
+ if (is_waiting_for_merchant_validation_ || !spec_)
return false;
if (ShouldShowShippingSection() &&
@@ -711,4 +763,17 @@ void PaymentRequestState::OnAddressNormalized(
app_locale_));
}
+bool PaymentRequestState::IsInTwa() const {
+ return !payment_request_delegate_->GetTwaPackageName().empty();
+}
+
+bool PaymentRequestState::GetCanMakePaymentValue() const {
+ return are_requested_methods_supported_ ||
+ can_make_payment_even_without_apps_;
+}
+
+bool PaymentRequestState::GetHasEnrolledInstrumentValue() const {
+ return has_enrolled_instrument_ || can_make_payment_even_without_apps_;
+}
+
} // namespace payments
diff --git a/chromium/components/payments/content/payment_request_state.h b/chromium/components/payments/content/payment_request_state.h
index 469b3104402..6b0cd174b31 100644
--- a/chromium/components/payments/content/payment_request_state.h
+++ b/chromium/components/payments/content/payment_request_state.h
@@ -114,6 +114,7 @@ class PaymentRequestState : public PaymentAppFactory::Delegate,
base::OnceCallback<void(bool methods_supported,
const std::string& error_message)>;
+ // The `spec` parameter should not be null.
PaymentRequestState(content::WebContents* web_contents,
content::RenderFrameHost* initiator_render_frame_host,
const GURL& top_level_origin,
@@ -132,12 +133,15 @@ class PaymentRequestState : public PaymentAppFactory::Delegate,
ContentPaymentRequestDelegate* GetPaymentRequestDelegate() const override;
void ShowProcessingSpinner() override;
PaymentRequestSpec* GetSpec() const override;
+ std::string GetTwaPackageName() const override;
const GURL& GetTopOrigin() override;
const GURL& GetFrameOrigin() override;
const url::Origin& GetFrameSecurityOrigin() override;
content::RenderFrameHost* GetInitiatorRenderFrameHost() const override;
const std::vector<mojom::PaymentMethodDataPtr>& GetMethodData()
const override;
+ std::unique_ptr<autofill::InternalAuthenticator> CreateInternalAuthenticator()
+ const override;
scoped_refptr<PaymentManifestWebDataService>
GetPaymentManifestWebDataService() const override;
const std::vector<autofill::AutofillProfile*>& GetBillingProfiles() override;
@@ -148,6 +152,7 @@ class PaymentRequestState : public PaymentAppFactory::Delegate,
void OnPaymentAppCreationError(const std::string& error_message) override;
bool SkipCreatingNativePaymentApps() const override;
void OnDoneCreatingPaymentApps() override;
+ void SetCanMakePaymentEvenWithoutApps() override;
// PaymentResponseHelper::Delegate
void OnPaymentResponseReady(
@@ -344,6 +349,12 @@ class PaymentRequestState : public PaymentAppFactory::Delegate,
void IncrementSelectionStatus(JourneyLogger::Section section,
SectionSelectionStatus selection_status);
+ // Returns whether the browser is currently in a TWA.
+ bool IsInTwa() const;
+
+ bool GetCanMakePaymentValue() const;
+ bool GetHasEnrolledInstrumentValue() const;
+
content::WebContents* web_contents_;
content::RenderFrameHost* initiator_render_frame_host_;
const GURL top_origin_;
@@ -380,8 +391,9 @@ class PaymentRequestState : public PaymentAppFactory::Delegate,
const std::string app_locale_;
+ base::WeakPtr<PaymentRequestSpec> spec_;
+
// Not owned. Never null. Will outlive this object.
- PaymentRequestSpec* spec_;
Delegate* delegate_;
autofill::PersonalDataManager* personal_data_manager_;
JourneyLogger* journey_logger_;
@@ -420,6 +432,15 @@ class PaymentRequestState : public PaymentAppFactory::Delegate,
// Whether PaymentRequest.show() was invoked with a user gesture.
bool is_show_user_gesture_ = false;
+ // If set to true, then both GetCanMakePaymentValue() and
+ // GetHasEnrolledInstrumentValue() will return true, regardless of presence of
+ // payment apps. This is used by secure payment confirmation, where
+ // PaymentRequest.canMakePayment() and PaymentRequesthasEnrolledInstrument()
+ // calls in JavaScript both return true without querying the SQLite database
+ // for instrument information and without querying the authenticator for
+ // credentials.
+ bool can_make_payment_even_without_apps_ = false;
+
base::WeakPtrFactory<PaymentRequestState> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(PaymentRequestState);
diff --git a/chromium/components/payments/content/payment_request_state_unittest.cc b/chromium/components/payments/content/payment_request_state_unittest.cc
index 6cfc72c6dbe..1f910a9e741 100644
--- a/chromium/components/payments/content/payment_request_state_unittest.cc
+++ b/chromium/components/payments/content/payment_request_state_unittest.cc
@@ -78,7 +78,7 @@ class PaymentRequestStateTest : public testing::Test,
std::move(options), std::move(details), std::move(method_data),
/*observer=*/nullptr, "en-US");
PaymentAppServiceFactory::SetForTesting(
- std::make_unique<PaymentAppService>());
+ std::make_unique<PaymentAppService>(/*context=*/nullptr));
state_ = std::make_unique<PaymentRequestState>(
/*web_contents=*/nullptr,
/*render_frame_host=*/nullptr, GURL("https://example.com"),
diff --git a/chromium/components/payments/content/payment_request_web_contents_manager.cc b/chromium/components/payments/content/payment_request_web_contents_manager.cc
index 3bcda4e278c..898f5e8cbc4 100644
--- a/chromium/components/payments/content/payment_request_web_contents_manager.cc
+++ b/chromium/components/payments/content/payment_request_web_contents_manager.cc
@@ -8,8 +8,10 @@
#include "base/check.h"
#include "components/payments/content/content_payment_request_delegate.h"
+#include "components/payments/content/payment_manifest_web_data_service.h"
#include "components/payments/content/payment_request.h"
#include "components/payments/content/payment_request_display_manager.h"
+#include "components/payments/core/features.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents.h"
@@ -33,7 +35,7 @@ void PaymentRequestWebContentsManager::CreatePaymentRequest(
mojo::PendingReceiver<payments::mojom::PaymentRequest> receiver,
PaymentRequest::ObserverForTest* observer_for_testing) {
auto new_request = std::make_unique<PaymentRequest>(
- render_frame_host, web_contents, std::move(delegate), this,
+ render_frame_host, web_contents, std::move(delegate), /*manager=*/this,
delegate->GetDisplayManager(), std::move(receiver), observer_for_testing);
PaymentRequest* request_ptr = new_request.get();
payment_requests_.insert(std::make_pair(request_ptr, std::move(new_request)));
@@ -54,6 +56,23 @@ void PaymentRequestWebContentsManager::DidStartNavigation(
it.second->DidStartMainFrameNavigationToDifferentDocument(
!navigation_handle->IsRendererInitiated());
}
+ payment_credential_ = nullptr;
+}
+
+void PaymentRequestWebContentsManager::RenderFrameDeleted(
+ content::RenderFrameHost* render_frame_host) {
+ // Two passes to avoid modifying the |payment_requests_| map while iterating
+ // over it.
+ std::vector<PaymentRequest*> obsolete;
+ for (auto& it : payment_requests_) {
+ if (content::RenderFrameHost::FromID(
+ it.second->initiator_frame_routing_id()) == render_frame_host) {
+ obsolete.push_back(it.first);
+ }
+ }
+ for (auto* request : obsolete) {
+ request->RenderFrameDeleted(render_frame_host);
+ }
}
void PaymentRequestWebContentsManager::DestroyRequest(PaymentRequest* request) {
@@ -61,6 +80,15 @@ void PaymentRequestWebContentsManager::DestroyRequest(PaymentRequest* request) {
payment_requests_.erase(request);
}
+void PaymentRequestWebContentsManager::CreatePaymentCredential(
+ content::GlobalFrameRoutingId initiator_frame_routing_id,
+ scoped_refptr<PaymentManifestWebDataService> web_data_sevice,
+ mojo::PendingReceiver<payments::mojom::PaymentCredential> receiver) {
+ payment_credential_ = std::make_unique<PaymentCredential>(
+ web_contents(), initiator_frame_routing_id, web_data_sevice,
+ std::move(receiver));
+}
+
PaymentRequestWebContentsManager::PaymentRequestWebContentsManager(
content::WebContents* web_contents)
: content::WebContentsObserver(web_contents) {}
diff --git a/chromium/components/payments/content/payment_request_web_contents_manager.h b/chromium/components/payments/content/payment_request_web_contents_manager.h
index 3f9e72b4adc..cee9dbdf7c1 100644
--- a/chromium/components/payments/content/payment_request_web_contents_manager.h
+++ b/chromium/components/payments/content/payment_request_web_contents_manager.h
@@ -9,10 +9,14 @@
#include <memory>
#include "base/macros.h"
+#include "base/memory/scoped_refptr.h"
+#include "components/payments/content/payment_credential.h"
#include "components/payments/content/payment_request.h"
+#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "third_party/blink/public/mojom/payments/payment_credential.mojom.h"
#include "third_party/blink/public/mojom/payments/payment_request.mojom.h"
namespace content {
@@ -24,6 +28,7 @@ class WebContents;
namespace payments {
class ContentPaymentRequestDelegate;
+class PaymentManifestWebDataService;
// This class owns the PaymentRequest associated with a given WebContents.
//
@@ -44,8 +49,8 @@ class PaymentRequestWebContentsManager
static PaymentRequestWebContentsManager* GetOrCreateForWebContents(
content::WebContents* web_contents);
- // Creates the PaymentRequest that will interact with this |render_frame_host|
- // and the associated |web_contents|.
+ // Creates the PaymentRequest that will interact with this `render_frame_host`
+ // and the associated `web_contents`.
void CreatePaymentRequest(
content::RenderFrameHost* render_frame_host,
content::WebContents* web_contents,
@@ -53,12 +58,20 @@ class PaymentRequestWebContentsManager
mojo::PendingReceiver<payments::mojom::PaymentRequest> receiver,
PaymentRequest::ObserverForTest* observer_for_testing);
- // Destroys the given |request|.
+ // Destroys the given `request`.
void DestroyRequest(PaymentRequest* request);
+ // Creates the mojo IPC endpoint that will receive requests from the renderer
+ // to store payment credential in user's profile.
+ void CreatePaymentCredential(
+ content::GlobalFrameRoutingId initiator_frame_routing_id,
+ scoped_refptr<PaymentManifestWebDataService> web_data_sevice,
+ mojo::PendingReceiver<payments::mojom::PaymentCredential> receiver);
+
// WebContentsObserver::
void DidStartNavigation(
content::NavigationHandle* navigation_handle) override;
+ void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override;
private:
explicit PaymentRequestWebContentsManager(content::WebContents* web_contents);
@@ -71,6 +84,8 @@ class PaymentRequestWebContentsManager
// the requests themselves call DestroyRequest().
std::map<PaymentRequest*, std::unique_ptr<PaymentRequest>> payment_requests_;
+ std::unique_ptr<PaymentCredential> payment_credential_;
+
WEB_CONTENTS_USER_DATA_KEY_DECL();
DISALLOW_COPY_AND_ASSIGN(PaymentRequestWebContentsManager);
diff --git a/chromium/components/payments/content/payment_response_helper.cc b/chromium/components/payments/content/payment_response_helper.cc
index 9f3c676fd4b..09e19de9971 100644
--- a/chromium/components/payments/content/payment_response_helper.cc
+++ b/chromium/components/payments/content/payment_response_helper.cc
@@ -33,12 +33,11 @@ PaymentResponseHelper::PaymentResponseHelper(
: app_locale_(app_locale),
is_waiting_for_shipping_address_normalization_(false),
is_waiting_for_instrument_details_(false),
- spec_(spec),
+ spec_(spec->GetWeakPtr()),
delegate_(delegate),
selected_app_(selected_app),
payment_request_delegate_(payment_request_delegate),
selected_contact_profile_(selected_contact_profile) {
- DCHECK(spec_);
DCHECK(selected_app_);
DCHECK(delegate_);
@@ -113,6 +112,9 @@ mojom::PayerDetailPtr PaymentResponseHelper::GeneratePayerDetail(
const autofill::AutofillProfile* selected_contact_profile) const {
mojom::PayerDetailPtr payer = mojom::PayerDetail::New();
+ if (!spec_)
+ return payer;
+
if (spec_->request_payer_name()) {
if (selected_app_->HandlesPayerName()) {
payer->name = payer_data_from_app_.payer_name;
@@ -159,6 +161,9 @@ void PaymentResponseHelper::GeneratePaymentResponse() {
DCHECK(!is_waiting_for_instrument_details_);
DCHECK(!is_waiting_for_shipping_address_normalization_);
+ if (!spec_)
+ return;
+
mojom::PaymentResponsePtr payment_response = mojom::PaymentResponse::New();
// Make sure that we return the method name that the merchant specified for
diff --git a/chromium/components/payments/content/payment_response_helper.h b/chromium/components/payments/content/payment_response_helper.h
index e3268660821..a761d655638 100644
--- a/chromium/components/payments/content/payment_response_helper.h
+++ b/chromium/components/payments/content/payment_response_helper.h
@@ -65,8 +65,9 @@ class PaymentResponseHelper
bool is_waiting_for_shipping_address_normalization_;
bool is_waiting_for_instrument_details_;
+ base::WeakPtr<PaymentRequestSpec> spec_;
+
// Not owned, cannot be null.
- PaymentRequestSpec* spec_;
Delegate* delegate_;
PaymentApp* selected_app_;
PaymentRequestDelegate* payment_request_delegate_;
diff --git a/chromium/components/payments/content/secure_payment_confirmation_app.cc b/chromium/components/payments/content/secure_payment_confirmation_app.cc
new file mode 100644
index 00000000000..654c3c35307
--- /dev/null
+++ b/chromium/components/payments/content/secure_payment_confirmation_app.cc
@@ -0,0 +1,318 @@
+// Copyright 2020 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/content/secure_payment_confirmation_app.h"
+
+#include <sstream>
+#include <utility>
+
+#include "base/base64.h"
+#include "base/check.h"
+#include "base/containers/flat_tree.h"
+#include "base/feature_list.h"
+#include "base/json/json_writer.h"
+#include "base/notreached.h"
+#include "base/strings/strcat.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "components/autofill/core/browser/payments/internal_authenticator.h"
+#include "components/payments/core/method_strings.h"
+#include "components/payments/core/payer_data.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_features.h"
+#include "crypto/sha2.h"
+#include "device/fido/fido_transport_protocol.h"
+#include "device/fido/fido_types.h"
+#include "device/fido/public_key_credential_descriptor.h"
+#include "url/url_constants.h"
+
+namespace payments {
+namespace {
+
+static constexpr int kDefaultTimeoutMinutes = 3;
+
+// Creates a SHA-256 hash over the Secure Payment Confirmation bundle, which is
+// a JSON string (without whitespace) with the following structure:
+// {
+// "merchantData" {
+// "merchantOrigin": "https://merchant.example",
+// "total": {
+// "currency": "CAD",
+// "value": "1.25",
+// },
+// },
+// "networkData": "YW=",
+// }
+// where "networkData" is the base64 encoding of the |networkData| specified in
+// the SecurePaymentConfirmationRequest.
+std::vector<uint8_t> GetSecurePaymentConfirmationChallenge(
+ const std::vector<uint8_t>& network_data,
+ const url::Origin& merchant_origin,
+ const mojom::PaymentCurrencyAmountPtr& amount) {
+ base::Value total(base::Value::Type::DICTIONARY);
+ total.SetKey("currency", base::Value(amount->currency));
+ total.SetKey("value", base::Value(amount->value));
+
+ base::Value merchant_data(base::Value::Type::DICTIONARY);
+ merchant_data.SetKey("merchantOrigin",
+ base::Value(merchant_origin.Serialize()));
+ merchant_data.SetKey("total", std::move(total));
+
+ base::Value transaction_data(base::Value::Type::DICTIONARY);
+ transaction_data.SetKey("networkData",
+ base::Value(base::Base64Encode(network_data)));
+ transaction_data.SetKey("merchantData", std::move(merchant_data));
+
+ // TODO(crbug.com/1123054): change to a more robust alternative that does not
+ // depend on the exact whitespace, escaping and ordering of the JSON
+ // serialization.
+ std::string json;
+ bool success = base::JSONWriter::Write(transaction_data, &json);
+ DCHECK(success) << "Failed to write JSON for " << transaction_data;
+
+ std::string sha256_hash = crypto::SHA256HashString(json);
+ std::vector<uint8_t> output_bytes(sha256_hash.begin(), sha256_hash.end());
+ return output_bytes;
+}
+
+} // namespace
+
+SecurePaymentConfirmationApp::SecurePaymentConfirmationApp(
+ content::WebContents* web_contents_to_observe,
+ const std::string& effective_relying_party_identity,
+ std::unique_ptr<SkBitmap> icon,
+ const base::string16& label,
+ std::vector<uint8_t> credential_id,
+ const url::Origin& merchant_origin,
+ const mojom::PaymentCurrencyAmountPtr& total,
+ mojom::SecurePaymentConfirmationRequestPtr request,
+ std::unique_ptr<autofill::InternalAuthenticator> authenticator)
+ : PaymentApp(/*icon_resource_id=*/0, PaymentApp::Type::INTERNAL),
+ content::WebContentsObserver(web_contents_to_observe),
+ authenticator_render_frame_host_pointer_do_not_dereference_(
+ authenticator->GetRenderFrameHost()),
+ effective_relying_party_identity_(effective_relying_party_identity),
+ icon_(std::move(icon)),
+ label_(label),
+ credential_id_(std::move(credential_id)),
+ encoded_credential_id_(base::Base64Encode(credential_id_)),
+ merchant_origin_(merchant_origin),
+ total_(total.Clone()),
+ request_(std::move(request)),
+ authenticator_(std::move(authenticator)) {
+ DCHECK_EQ(web_contents_to_observe->GetMainFrame(),
+ authenticator_render_frame_host_pointer_do_not_dereference_);
+ DCHECK(!credential_id_.empty());
+
+ app_method_names_.insert(methods::kSecurePaymentConfirmation);
+}
+
+SecurePaymentConfirmationApp::~SecurePaymentConfirmationApp() = default;
+
+void SecurePaymentConfirmationApp::InvokePaymentApp(Delegate* delegate) {
+ if (!authenticator_)
+ return;
+
+ auto options = blink::mojom::PublicKeyCredentialRequestOptions::New();
+ options->relying_party_id = effective_relying_party_identity_;
+ options->timeout = request_->timeout.has_value()
+ ? request_->timeout.value()
+ : base::TimeDelta::FromMinutes(kDefaultTimeoutMinutes);
+ options->user_verification = device::UserVerificationRequirement::kRequired;
+ std::vector<device::PublicKeyCredentialDescriptor> credentials;
+
+ if (base::FeatureList::IsEnabled(features::kSecurePaymentConfirmationDebug)) {
+ options->user_verification =
+ device::UserVerificationRequirement::kPreferred;
+ // The `device::PublicKeyCredentialDescriptor` constructor with 2 parameters
+ // enables authentication through all protocols.
+ credentials.emplace_back(device::CredentialType::kPublicKey,
+ credential_id_);
+ } else {
+ // Enable authentication only through internal authenticators by default.
+ credentials.emplace_back(device::CredentialType::kPublicKey, credential_id_,
+ base::flat_set<device::FidoTransportProtocol>{
+ device::FidoTransportProtocol::kInternal});
+ }
+
+ options->allow_credentials = std::move(credentials);
+
+ // Create a new challenge that is a hash of the transaction data.
+ options->challenge = GetSecurePaymentConfirmationChallenge(
+ request_->network_data, merchant_origin_, total_);
+
+ // We are nullifying the security check by design, and the origin that created
+ // the credential isn't saved anywhere.
+ authenticator_->SetEffectiveOrigin(url::Origin::Create(
+ GURL(base::StrCat({url::kHttpsScheme, url::kStandardSchemeSeparator,
+ effective_relying_party_identity_}))));
+
+ authenticator_->GetAssertion(
+ std::move(options),
+ base::BindOnce(&SecurePaymentConfirmationApp::OnGetAssertion,
+ weak_ptr_factory_.GetWeakPtr(), delegate));
+}
+
+bool SecurePaymentConfirmationApp::IsCompleteForPayment() const {
+ return true;
+}
+
+uint32_t SecurePaymentConfirmationApp::GetCompletenessScore() const {
+ // This value is used for sorting multiple apps, but this app always appears
+ // on its own.
+ return 0;
+}
+
+bool SecurePaymentConfirmationApp::CanPreselect() const {
+ return true;
+}
+
+base::string16 SecurePaymentConfirmationApp::GetMissingInfoLabel() const {
+ NOTREACHED();
+ return base::string16();
+}
+
+bool SecurePaymentConfirmationApp::HasEnrolledInstrument() const {
+ // If there's no platform authenticator, then the factory should not create
+ // this app. Therefore, this function can always return true.
+ return true;
+}
+
+void SecurePaymentConfirmationApp::RecordUse() {
+ NOTIMPLEMENTED();
+}
+
+bool SecurePaymentConfirmationApp::NeedsInstallation() const {
+ return false;
+}
+
+std::string SecurePaymentConfirmationApp::GetId() const {
+ return encoded_credential_id_;
+}
+
+base::string16 SecurePaymentConfirmationApp::GetLabel() const {
+ return label_;
+}
+
+base::string16 SecurePaymentConfirmationApp::GetSublabel() const {
+ return base::string16();
+}
+
+const SkBitmap* SecurePaymentConfirmationApp::icon_bitmap() const {
+ return icon_.get();
+}
+
+bool SecurePaymentConfirmationApp::IsValidForModifier(
+ const std::string& method,
+ bool supported_networks_specified,
+ const std::set<std::string>& supported_networks) const {
+ bool is_valid = false;
+ IsValidForPaymentMethodIdentifier(method, &is_valid);
+ return is_valid;
+}
+
+base::WeakPtr<PaymentApp> SecurePaymentConfirmationApp::AsWeakPtr() {
+ return weak_ptr_factory_.GetWeakPtr();
+}
+
+bool SecurePaymentConfirmationApp::HandlesShippingAddress() const {
+ return false;
+}
+
+bool SecurePaymentConfirmationApp::HandlesPayerName() const {
+ return false;
+}
+
+bool SecurePaymentConfirmationApp::HandlesPayerEmail() const {
+ return false;
+}
+
+bool SecurePaymentConfirmationApp::HandlesPayerPhone() const {
+ return false;
+}
+
+bool SecurePaymentConfirmationApp::IsWaitingForPaymentDetailsUpdate() const {
+ return false;
+}
+
+void SecurePaymentConfirmationApp::UpdateWith(
+ mojom::PaymentRequestDetailsUpdatePtr details_update) {
+ NOTREACHED();
+}
+
+void SecurePaymentConfirmationApp::OnPaymentDetailsNotUpdated() {
+ NOTREACHED();
+}
+
+void SecurePaymentConfirmationApp::AbortPaymentApp(
+ base::OnceCallback<void(bool)> abort_callback) {
+ std::move(abort_callback).Run(/*abort_success=*/false);
+}
+
+void SecurePaymentConfirmationApp::RenderFrameDeleted(
+ content::RenderFrameHost* render_frame_host) {
+ if (authenticator_render_frame_host_pointer_do_not_dereference_ ==
+ render_frame_host) {
+ // The authenticator requires to be deleted before the render frame.
+ authenticator_.reset();
+ }
+}
+
+void SecurePaymentConfirmationApp::OnGetAssertion(
+ Delegate* delegate,
+ blink::mojom::AuthenticatorStatus status,
+ blink::mojom::GetAssertionAuthenticatorResponsePtr response) {
+ if (status != blink::mojom::AuthenticatorStatus::SUCCESS || !response) {
+ std::stringstream status_string_stream;
+ status_string_stream << status;
+ delegate->OnInstrumentDetailsError(base::StringPrintf(
+ "Authenticator returned %s.", status_string_stream.str().c_str()));
+ return;
+ }
+
+ // Serialize response into a JSON string. Browser will pass this string over
+ // Mojo IPC into Blink, which will parse it into a JavaScript object for the
+ // merchant.
+ auto info_json = std::make_unique<base::DictionaryValue>();
+ if (response->info) {
+ info_json->SetString("id", response->info->id);
+ info_json->SetString("client_data_json",
+ base::Base64Encode(response->info->client_data_json));
+ info_json->SetString(
+ "authenticator_data",
+ base::Base64Encode(response->info->authenticator_data));
+ }
+
+ auto prf_results_json = std::make_unique<base::DictionaryValue>();
+ if (response->prf_results) {
+ DCHECK(!response->prf_results->id.has_value());
+ prf_results_json->SetString(
+ "first", base::Base64Encode(response->prf_results->first));
+ if (response->prf_results->second) {
+ prf_results_json->SetString(
+ "second", base::Base64Encode(*response->prf_results->second));
+ }
+ }
+
+ base::DictionaryValue json;
+ json.Set("info", std::move(info_json));
+ json.SetString("signature", base::Base64Encode(response->signature));
+ if (response->user_handle.has_value()) {
+ json.SetString("user_handle",
+ base::Base64Encode(response->user_handle.value()));
+ }
+ json.SetBoolean("echo_appid_extension", response->echo_appid_extension);
+ json.SetBoolean("appid_extension", response->appid_extension);
+ json.SetBoolean("echo_prf", response->echo_prf);
+ json.Set("prf_results", std::move(prf_results_json));
+ json.SetBoolean("prf_not_evaluated", response->echo_prf);
+
+ std::string json_serialized_response;
+ base::JSONWriter::Write(json, &json_serialized_response);
+ delegate->OnInstrumentDetailsReady(methods::kSecurePaymentConfirmation,
+ json_serialized_response, PayerData());
+}
+
+} // namespace payments
diff --git a/chromium/components/payments/content/secure_payment_confirmation_app.h b/chromium/components/payments/content/secure_payment_confirmation_app.h
new file mode 100644
index 00000000000..bb7b89d101c
--- /dev/null
+++ b/chromium/components/payments/content/secure_payment_confirmation_app.h
@@ -0,0 +1,115 @@
+// Copyright 2020 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_SECURE_PAYMENT_CONFIRMATION_APP_H_
+#define COMPONENTS_PAYMENTS_CONTENT_SECURE_PAYMENT_CONFIRMATION_APP_H_
+
+#include "components/payments/content/payment_app.h"
+
+#include <stdint.h>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string16.h"
+#include "components/payments/content/secure_payment_confirmation_controller.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "third_party/blink/public/mojom/payments/payment_request.mojom.h"
+#include "third_party/blink/public/mojom/webauthn/authenticator.mojom.h"
+#include "url/origin.h"
+
+class SkBitmap;
+
+namespace autofill {
+class InternalAuthenticator;
+} // namespace autofill
+
+namespace content {
+class RenderFrameHost;
+class WebContents;
+} // namespace content
+
+namespace payments {
+
+class SecurePaymentConfirmationApp : public PaymentApp,
+ public content::WebContentsObserver {
+ public:
+ // Please use `std::move()` for the `credential_id` parameter to avoid extra
+ // copies.
+ SecurePaymentConfirmationApp(
+ content::WebContents* web_contents_to_observe,
+ const std::string& effective_relying_party_identity,
+ std::unique_ptr<SkBitmap> icon,
+ const base::string16& label,
+ std::vector<uint8_t> credential_id,
+ const url::Origin& merchant_origin,
+ const mojom::PaymentCurrencyAmountPtr& total,
+ mojom::SecurePaymentConfirmationRequestPtr request,
+ std::unique_ptr<autofill::InternalAuthenticator> authenticator);
+ ~SecurePaymentConfirmationApp() override;
+
+ SecurePaymentConfirmationApp(const SecurePaymentConfirmationApp& other) =
+ delete;
+ SecurePaymentConfirmationApp& operator=(
+ const SecurePaymentConfirmationApp& other) = delete;
+
+ // PaymentApp implementation.
+ void InvokePaymentApp(Delegate* delegate) override;
+ bool IsCompleteForPayment() const override;
+ uint32_t GetCompletenessScore() const override;
+ bool CanPreselect() const override;
+ base::string16 GetMissingInfoLabel() const override;
+ bool HasEnrolledInstrument() const override;
+ void RecordUse() override;
+ bool NeedsInstallation() const override;
+ std::string GetId() const override;
+ base::string16 GetLabel() const override;
+ base::string16 GetSublabel() const override;
+ const SkBitmap* icon_bitmap() const override;
+ bool IsValidForModifier(
+ const std::string& method,
+ bool supported_networks_specified,
+ const std::set<std::string>& supported_networks) const override;
+ base::WeakPtr<PaymentApp> AsWeakPtr() override;
+ bool HandlesShippingAddress() const override;
+ bool HandlesPayerName() const override;
+ bool HandlesPayerEmail() const override;
+ bool HandlesPayerPhone() const override;
+ bool IsWaitingForPaymentDetailsUpdate() const override;
+ void UpdateWith(
+ mojom::PaymentRequestDetailsUpdatePtr details_update) override;
+ void OnPaymentDetailsNotUpdated() override;
+ void AbortPaymentApp(base::OnceCallback<void(bool)> abort_callback) override;
+
+ // WebContentsObserver implementation.
+ void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override;
+
+ private:
+ void OnGetAssertion(
+ Delegate* delegate,
+ blink::mojom::AuthenticatorStatus status,
+ blink::mojom::GetAssertionAuthenticatorResponsePtr response);
+
+ // Used only for comparison with the RenderFrameHost pointer in
+ // RenderFrameDeleted() method.
+ const content::RenderFrameHost* const
+ authenticator_render_frame_host_pointer_do_not_dereference_;
+
+ const std::string effective_relying_party_identity_;
+ const std::unique_ptr<SkBitmap> icon_;
+ const base::string16 label_;
+ const std::vector<uint8_t> credential_id_;
+ const std::string encoded_credential_id_;
+ const url::Origin merchant_origin_;
+ const mojom::PaymentCurrencyAmountPtr total_;
+ const mojom::SecurePaymentConfirmationRequestPtr request_;
+ std::unique_ptr<autofill::InternalAuthenticator> authenticator_;
+
+ base::WeakPtrFactory<SecurePaymentConfirmationApp> weak_ptr_factory_{this};
+};
+
+} // namespace payments
+
+#endif // COMPONENTS_PAYMENTS_CONTENT_SECURE_PAYMENT_CONFIRMATION_APP_H_
diff --git a/chromium/components/payments/content/secure_payment_confirmation_app_factory.cc b/chromium/components/payments/content/secure_payment_confirmation_app_factory.cc
new file mode 100644
index 00000000000..4ca453903ff
--- /dev/null
+++ b/chromium/components/payments/content/secure_payment_confirmation_app_factory.cc
@@ -0,0 +1,239 @@
+// Copyright 2020 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/content/secure_payment_confirmation_app_factory.h"
+
+#include <stdint.h>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/check.h"
+#include "base/feature_list.h"
+#include "base/stl_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "components/autofill/core/browser/payments/internal_authenticator.h"
+#include "components/payments/content/payment_manifest_web_data_service.h"
+#include "components/payments/content/payment_request_spec.h"
+#include "components/payments/content/secure_payment_confirmation_app.h"
+#include "components/payments/core/method_strings.h"
+#include "components/payments/core/native_error_strings.h"
+#include "components/payments/core/secure_payment_confirmation_instrument.h"
+#include "components/webdata/common/web_data_results.h"
+#include "components/webdata/common/web_data_service_base.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/common/content_features.h"
+#include "services/data_decoder/public/cpp/decode_image.h"
+#include "third_party/blink/public/mojom/payments/payment_request.mojom.h"
+#include "url/origin.h"
+
+namespace payments {
+namespace {
+
+// Arbitrarily chosen limit of 1 hour. Keep in sync with
+// secure_payment_confirmation_helper.cc.
+constexpr int64_t kMaxTimeoutInMilliseconds = 1000 * 60 * 60;
+
+bool IsValid(const mojom::SecurePaymentConfirmationRequestPtr& request,
+ std::string* error_message) {
+ // `request` can be null when the feature is disabled in Blink.
+ if (!request)
+ return false;
+
+ if (request->credential_ids.empty()) {
+ *error_message = errors::kCredentialIdsRequired;
+ return false;
+ }
+
+ for (const auto& credential_id : request->credential_ids) {
+ if (credential_id.empty()) {
+ *error_message = errors::kCredentialIdsRequired;
+ return false;
+ }
+ }
+
+ if (request->timeout.has_value() &&
+ request->timeout.value().InMilliseconds() > kMaxTimeoutInMilliseconds) {
+ *error_message = errors::kTimeoutTooLong;
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace
+
+void SecurePaymentConfirmationAppFactory::
+ OnIsUserVerifyingPlatformAuthenticatorAvailable(
+ base::WeakPtr<PaymentAppFactory::Delegate> delegate,
+ mojom::SecurePaymentConfirmationRequestPtr request,
+ std::unique_ptr<autofill::InternalAuthenticator> authenticator,
+ bool is_available) {
+ if (!delegate)
+ return;
+
+ if (!is_available && !base::FeatureList::IsEnabled(
+ features::kSecurePaymentConfirmationDebug)) {
+ delegate->OnDoneCreatingPaymentApps();
+ return;
+ }
+
+ // Regardless of whether `web_data_service` has any apps, canMakePayment() and
+ // hasEnrolledInstrument() should return true when a user-verifying platform
+ // authenticator device is available.
+ delegate->SetCanMakePaymentEvenWithoutApps();
+
+ scoped_refptr<payments::PaymentManifestWebDataService> web_data_service =
+ delegate->GetPaymentManifestWebDataService();
+ if (!web_data_service) {
+ delegate->OnDoneCreatingPaymentApps();
+ return;
+ }
+
+ WebDataServiceBase::Handle handle =
+ web_data_service->GetSecurePaymentConfirmationInstruments(
+ std::move(request->credential_ids), this);
+ requests_[handle] = std::make_unique<Request>(delegate, std::move(request),
+ std::move(authenticator));
+}
+
+SecurePaymentConfirmationAppFactory::SecurePaymentConfirmationAppFactory()
+ : PaymentAppFactory(PaymentApp::Type::INTERNAL) {}
+
+SecurePaymentConfirmationAppFactory::~SecurePaymentConfirmationAppFactory() =
+ default;
+
+void SecurePaymentConfirmationAppFactory::Create(
+ base::WeakPtr<Delegate> delegate) {
+ DCHECK(delegate);
+
+ PaymentRequestSpec* spec = delegate->GetSpec();
+ if (!spec || !base::Contains(spec->payment_method_identifiers_set(),
+ methods::kSecurePaymentConfirmation)) {
+ delegate->OnDoneCreatingPaymentApps();
+ return;
+ }
+
+ for (const mojom::PaymentMethodDataPtr& method_data : spec->method_data()) {
+ if (method_data->supported_method == methods::kSecurePaymentConfirmation) {
+ std::string error_message;
+ if (!IsValid(method_data->secure_payment_confirmation, &error_message)) {
+ if (!error_message.empty())
+ delegate->OnPaymentAppCreationError(error_message);
+ delegate->OnDoneCreatingPaymentApps();
+ return;
+ }
+
+ std::unique_ptr<autofill::InternalAuthenticator> authenticator =
+ delegate->CreateInternalAuthenticator();
+
+ authenticator->IsUserVerifyingPlatformAuthenticatorAvailable(
+ base::BindOnce(&SecurePaymentConfirmationAppFactory::
+ OnIsUserVerifyingPlatformAuthenticatorAvailable,
+ weak_ptr_factory_.GetWeakPtr(), delegate,
+ method_data->secure_payment_confirmation.Clone(),
+ std::move(authenticator)));
+ return;
+ }
+ }
+
+ delegate->OnDoneCreatingPaymentApps();
+}
+
+struct SecurePaymentConfirmationAppFactory::Request
+ : public content::WebContentsObserver {
+ Request(base::WeakPtr<PaymentAppFactory::Delegate> delegate,
+ mojom::SecurePaymentConfirmationRequestPtr mojo_request,
+ std::unique_ptr<autofill::InternalAuthenticator> authenticator)
+ : content::WebContentsObserver(delegate->GetWebContents()),
+ delegate(delegate),
+ mojo_request(std::move(mojo_request)),
+ authenticator(std::move(authenticator)) {}
+
+ ~Request() override = default;
+
+ Request(const Request& other) = delete;
+ Request& operator=(const Request& other) = delete;
+
+ base::WeakPtr<PaymentAppFactory::Delegate> delegate;
+ mojom::SecurePaymentConfirmationRequestPtr mojo_request;
+ std::unique_ptr<autofill::InternalAuthenticator> authenticator;
+};
+
+void SecurePaymentConfirmationAppFactory::OnWebDataServiceRequestDone(
+ WebDataServiceBase::Handle handle,
+ std::unique_ptr<WDTypedResult> result) {
+ auto iterator = requests_.find(handle);
+ if (iterator == requests_.end())
+ return;
+
+ std::unique_ptr<Request> request = std::move(iterator->second);
+ requests_.erase(iterator);
+ DCHECK(request.get());
+ if (!request->delegate || !request->web_contents())
+ return;
+
+ if (!result || result->GetType() != SECURE_PAYMENT_CONFIRMATION) {
+ request->delegate->OnDoneCreatingPaymentApps();
+ return;
+ }
+
+ std::vector<std::unique_ptr<SecurePaymentConfirmationInstrument>>
+ instruments = static_cast<WDResult<
+ std::vector<std::unique_ptr<SecurePaymentConfirmationInstrument>>>*>(
+ result.get())
+ ->GetValue();
+ if (instruments.empty()) {
+ request->delegate->OnDoneCreatingPaymentApps();
+ return;
+ }
+
+ // For the pilot phase, arbitrarily use the first matching instrument.
+ // TODO(https://crbug.com/1110320): Handle multiple instruments.
+ std::unique_ptr<SecurePaymentConfirmationInstrument> instrument =
+ std::move(instruments.front());
+
+ auto* instrument_ptr = instrument.get();
+ // Decode the icon in a sandboxed process off the main thread.
+ data_decoder::DecodeImageIsolated(
+ instrument_ptr->icon, data_decoder::mojom::ImageCodec::DEFAULT,
+ /*shrink_to_fit=*/false, data_decoder::kDefaultMaxSizeInBytes,
+ /*desired_image_frame_size=*/gfx::Size(),
+ base::BindOnce(&SecurePaymentConfirmationAppFactory::OnAppIconDecoded,
+ weak_ptr_factory_.GetWeakPtr(), std::move(instrument),
+ std::move(request)));
+}
+
+void SecurePaymentConfirmationAppFactory::OnAppIconDecoded(
+ std::unique_ptr<SecurePaymentConfirmationInstrument> instrument,
+ std::unique_ptr<Request> request,
+ const SkBitmap& decoded_icon) {
+ DCHECK(request);
+ if (!request->delegate || !request->web_contents() ||
+ !request->delegate->GetSpec() ||
+ request->authenticator->GetRenderFrameHost() !=
+ request->web_contents()->GetMainFrame()) {
+ request->delegate->OnDoneCreatingPaymentApps();
+ return;
+ }
+
+ DCHECK(!decoded_icon.drawsNothing());
+ auto icon = std::make_unique<SkBitmap>(decoded_icon);
+
+ request->delegate->OnPaymentAppCreated(
+ std::make_unique<SecurePaymentConfirmationApp>(
+ request->web_contents(), instrument->relying_party_id,
+ std::move(icon), instrument->label,
+ std::move(instrument->credential_id),
+ url::Origin::Create(request->delegate->GetTopOrigin()),
+ request->delegate->GetSpec()->details().total->amount,
+ std::move(request->mojo_request), std::move(request->authenticator)));
+
+ request->delegate->OnDoneCreatingPaymentApps();
+}
+
+} // namespace payments
diff --git a/chromium/components/payments/content/secure_payment_confirmation_app_factory.h b/chromium/components/payments/content/secure_payment_confirmation_app_factory.h
new file mode 100644
index 00000000000..3eab505598b
--- /dev/null
+++ b/chromium/components/payments/content/secure_payment_confirmation_app_factory.h
@@ -0,0 +1,59 @@
+// Copyright 2020 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_SECURE_PAYMENT_CONFIRMATION_APP_FACTORY_H_
+#define COMPONENTS_PAYMENTS_CONTENT_SECURE_PAYMENT_CONFIRMATION_APP_FACTORY_H_
+
+#include <map>
+#include <memory>
+
+#include "base/memory/weak_ptr.h"
+#include "components/payments/content/payment_app_factory.h"
+#include "components/webdata/common/web_data_service_consumer.h"
+
+namespace payments {
+
+struct SecurePaymentConfirmationInstrument;
+
+class SecurePaymentConfirmationAppFactory : public PaymentAppFactory,
+ public WebDataServiceConsumer {
+ public:
+ SecurePaymentConfirmationAppFactory();
+ ~SecurePaymentConfirmationAppFactory() override;
+
+ SecurePaymentConfirmationAppFactory(
+ const SecurePaymentConfirmationAppFactory& other) = delete;
+ SecurePaymentConfirmationAppFactory& operator=(
+ const SecurePaymentConfirmationAppFactory& other) = delete;
+
+ // PaymentAppFactory:
+ void Create(base::WeakPtr<Delegate> delegate) override;
+
+ private:
+ struct Request;
+
+ // WebDataServiceConsumer:
+ void OnWebDataServiceRequestDone(
+ WebDataServiceBase::Handle handle,
+ std::unique_ptr<WDTypedResult> result) override;
+
+ void OnIsUserVerifyingPlatformAuthenticatorAvailable(
+ base::WeakPtr<PaymentAppFactory::Delegate> delegate,
+ mojom::SecurePaymentConfirmationRequestPtr request,
+ std::unique_ptr<autofill::InternalAuthenticator> authenticator,
+ bool is_available);
+
+ void OnAppIconDecoded(
+ std::unique_ptr<SecurePaymentConfirmationInstrument> instrument,
+ std::unique_ptr<Request> request,
+ const SkBitmap& decoded_image);
+
+ std::map<WebDataServiceBase::Handle, std::unique_ptr<Request>> requests_;
+ base::WeakPtrFactory<SecurePaymentConfirmationAppFactory> weak_ptr_factory_{
+ this};
+};
+
+} // namespace payments
+
+#endif // COMPONENTS_PAYMENTS_CONTENT_SECURE_PAYMENT_CONFIRMATION_APP_FACTORY_H_
diff --git a/chromium/components/payments/content/secure_payment_confirmation_app_unittest.cc b/chromium/components/payments/content/secure_payment_confirmation_app_unittest.cc
new file mode 100644
index 00000000000..1a00229a37b
--- /dev/null
+++ b/chromium/components/payments/content/secure_payment_confirmation_app_unittest.cc
@@ -0,0 +1,129 @@
+// Copyright 2020 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/content/secure_payment_confirmation_app.h"
+
+#include <memory>
+
+#include "base/base64.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/autofill/core/browser/payments/internal_authenticator.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/public/test/test_web_contents_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/mojom/webauthn/authenticator.mojom.h"
+#include "url/origin.h"
+
+namespace payments {
+namespace {
+
+using ::testing::_;
+using ::testing::Eq;
+
+static constexpr char kNetworkDataBase64[] = "aaaa";
+static constexpr char kCredentialIdBase64[] = "cccc";
+
+class MockAuthenticator : public autofill::InternalAuthenticator {
+ public:
+ MockAuthenticator()
+ : web_contents_(web_contents_factory_.CreateWebContents(&context_)) {}
+ ~MockAuthenticator() override = default;
+
+ MOCK_METHOD1(SetEffectiveOrigin, void(const url::Origin&));
+ MOCK_METHOD2(
+ MakeCredential,
+ void(blink::mojom::PublicKeyCredentialCreationOptionsPtr options,
+ blink::mojom::Authenticator::MakeCredentialCallback callback));
+ MOCK_METHOD1(IsUserVerifyingPlatformAuthenticatorAvailable,
+ void(blink::mojom::Authenticator::
+ IsUserVerifyingPlatformAuthenticatorAvailableCallback));
+ MOCK_METHOD0(Cancel, void());
+ MOCK_METHOD1(VerifyChallenge, void(const std::vector<uint8_t>&));
+
+ content::RenderFrameHost* GetRenderFrameHost() override {
+ return web_contents_->GetMainFrame();
+ }
+
+ // Implements an autofill::InternalAuthenticator method to delegate fields of
+ // |options| to gmock methods for easier verification.
+ void GetAssertion(
+ blink::mojom::PublicKeyCredentialRequestOptionsPtr options,
+ blink::mojom::Authenticator::GetAssertionCallback callback) override {
+ VerifyChallenge(options->challenge);
+ }
+
+ content::WebContents* web_contents() { return web_contents_; }
+
+ private:
+ content::BrowserTaskEnvironment task_environment_;
+ content::TestBrowserContext context_;
+ content::TestWebContentsFactory web_contents_factory_;
+ content::WebContents* web_contents_; // Owned by `web_contents_factory_`.
+};
+
+class SecurePaymentConfirmationAppTest : public testing::Test {
+ protected:
+ SecurePaymentConfirmationAppTest()
+ : label_(base::ASCIIToUTF16("test instrument")),
+ total_(mojom::PaymentCurrencyAmount::New()) {
+ total_->currency = "USD";
+ total_->value = "1.25";
+ }
+
+ void SetUp() override {
+ ASSERT_TRUE(base::Base64Decode(kNetworkDataBase64, &network_data_bytes_));
+ ASSERT_TRUE(base::Base64Decode(kCredentialIdBase64, &credential_id_bytes_));
+ }
+
+ mojom::SecurePaymentConfirmationRequestPtr MakeRequest() {
+ auto request = mojom::SecurePaymentConfirmationRequest::New();
+ request->network_data = std::vector<uint8_t>(network_data_bytes_.begin(),
+ network_data_bytes_.end());
+ return request;
+ }
+
+ base::string16 label_;
+ mojom::PaymentCurrencyAmountPtr total_;
+ std::string network_data_bytes_;
+ std::string credential_id_bytes_;
+};
+
+TEST_F(SecurePaymentConfirmationAppTest, Smoke) {
+ std::vector<uint8_t> credential_id(credential_id_bytes_.begin(),
+ credential_id_bytes_.end());
+
+ auto authenticator = std::make_unique<MockAuthenticator>();
+ MockAuthenticator* mock_authenticator = authenticator.get();
+ content::WebContents* web_contents = authenticator->web_contents();
+
+ SecurePaymentConfirmationApp app(
+ web_contents, "effective_rp.example",
+ /*icon=*/std::make_unique<SkBitmap>(), label_, std::move(credential_id),
+ url::Origin::Create(GURL("https://merchant.example")), total_,
+ MakeRequest(), std::move(authenticator));
+
+ EXPECT_CALL(*mock_authenticator, SetEffectiveOrigin(Eq(url::Origin::Create(
+ GURL("https://effective_rp.example")))));
+
+ // This is the SHA-256 hash of the serialized JSON string:
+ // {"merchantData":{"merchantOrigin":"https://merchant.example","total":
+ // {"currency":"USD","value":"1.25"}},"networkData":"aaaa"}
+ //
+ // To update the test expectation, open
+ // //components/test/data/payments/secure_payment_confirmation_debut.html in a
+ // browser and follow the instructions.
+ std::vector<uint8_t> expected_bytes = {
+ 240, 123, 37, 51, 16, 34, 244, 220, 166, 179, 139,
+ 85, 229, 152, 242, 133, 88, 44, 222, 133, 49, 97,
+ 146, 20, 207, 119, 43, 142, 171, 239, 125, 250};
+ EXPECT_CALL(*mock_authenticator, VerifyChallenge(Eq(expected_bytes)));
+ app.InvokePaymentApp(/*delegate=*/nullptr);
+}
+
+} // namespace
+} // namespace payments
diff --git a/chromium/components/payments/content/secure_payment_confirmation_controller.cc b/chromium/components/payments/content/secure_payment_confirmation_controller.cc
new file mode 100644
index 00000000000..2b96947ad68
--- /dev/null
+++ b/chromium/components/payments/content/secure_payment_confirmation_controller.cc
@@ -0,0 +1,207 @@
+// Copyright 2020 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/content/secure_payment_confirmation_controller.h"
+
+#include "base/bind.h"
+#include "base/check.h"
+#include "base/location.h"
+#include "base/strings/strcat.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "components/payments/content/payment_request.h"
+#include "components/payments/core/currency_formatter.h"
+#include "components/payments/core/method_strings.h"
+#include "components/strings/grit/components_strings.h"
+#include "components/url_formatter/elide_url.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace payments {
+
+SecurePaymentConfirmationController::SecurePaymentConfirmationController(
+ base::WeakPtr<PaymentRequest> request)
+ : request_(request) {
+ DCHECK(request_);
+}
+
+SecurePaymentConfirmationController::~SecurePaymentConfirmationController() =
+ default;
+
+void SecurePaymentConfirmationController::ShowDialog() {
+#if defined(OS_ANDROID)
+ NOTREACHED();
+#endif // OS_ANDROID
+
+ if (!request_ || !request_->spec())
+ return;
+
+ if (!request_->state()->IsInitialized()) {
+ number_of_initialization_tasks_++;
+ request_->state()->AddInitializationObserver(this);
+ }
+
+ if (!request_->spec()->IsInitialized()) {
+ number_of_initialization_tasks_++;
+ request_->spec()->AddInitializationObserver(this);
+ }
+
+ if (number_of_initialization_tasks_ == 0)
+ SetupModelAndShowDialogIfApplicable();
+}
+
+void SecurePaymentConfirmationController::
+ SetupModelAndShowDialogIfApplicable() {
+ DCHECK(!view_);
+ // If no apps are available then don't show any UI. The payment_request.cc
+ // code will reject the PaymentRequest.show() call with appropriate error
+ // message on its own.
+ if (!request_ || !request_->state() || !request_->spec() ||
+ request_->state()->available_apps().empty()) {
+ return;
+ }
+
+ if (!request_->web_contents() || !request_->state()->selected_app() ||
+ request_->state()->selected_app()->type() != PaymentApp::Type::INTERNAL ||
+ request_->state()->selected_app()->GetAppMethodNames().size() != 1 ||
+ *request_->state()->selected_app()->GetAppMethodNames().begin() !=
+ methods::kSecurePaymentConfirmation ||
+ request_->state()->available_apps().size() != 1 || !request_->spec() ||
+ !request_->spec()->IsSecurePaymentConfirmationRequested() ||
+ request_->spec()->request_shipping() ||
+ request_->spec()->request_payer_name() ||
+ request_->spec()->request_payer_email() ||
+ request_->spec()->request_payer_phone()) {
+ OnCancel();
+ return;
+ }
+
+ model_.set_verify_button_label(l10n_util::GetStringUTF16(
+ IDS_SECURE_PAYMENT_CONFIRMATION_VERIFY_BUTTON_LABEL));
+ model_.set_cancel_button_label(l10n_util::GetStringUTF16(IDS_CANCEL));
+ model_.set_progress_bar_visible(false);
+
+ model_.set_title(l10n_util::GetStringUTF16(
+ IDS_SECURE_PAYMENT_CONFIRMATION_VERIFY_PURCHASE));
+
+ model_.set_merchant_label(
+ l10n_util::GetStringUTF16(IDS_SECURE_PAYMENT_CONFIRMATION_STORE_LABEL));
+ model_.set_merchant_value(url_formatter::FormatUrlForSecurityDisplay(
+ request_->state()->GetTopOrigin(),
+ url_formatter::SchemeDisplay::OMIT_CRYPTOGRAPHIC));
+
+ model_.set_instrument_label(l10n_util::GetStringUTF16(
+ IDS_PAYMENT_REQUEST_PAYMENT_METHOD_SECTION_NAME));
+ PaymentApp* app = request_->state()->selected_app();
+ model_.set_instrument_value(app->GetLabel());
+ model_.set_instrument_icon(app->icon_bitmap());
+
+ model_.set_total_label(
+ l10n_util::GetStringUTF16(IDS_SECURE_PAYMENT_CONFIRMATION_TOTAL_LABEL));
+ const mojom::PaymentItemPtr& total = request_->spec()->GetTotal(app);
+ base::string16 total_value = base::UTF8ToUTF16(total->amount->currency);
+ model_.set_total_value(base::StrCat(
+ {base::UTF8ToUTF16(total->amount->currency), base::ASCIIToUTF16(" "),
+ CurrencyFormatter(total->amount->currency,
+ request_->state()->GetApplicationLocale())
+ .Format(total->amount->value)}));
+
+ view_ = SecurePaymentConfirmationView::Create();
+ view_->ShowDialog(
+ request_->web_contents(), model_.GetWeakPtr(),
+ base::BindOnce(&SecurePaymentConfirmationController::OnConfirm,
+ weak_ptr_factory_.GetWeakPtr()),
+ base::BindOnce(&SecurePaymentConfirmationController::OnCancel,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void SecurePaymentConfirmationController::RetryDialog() {
+ // Retry is not supported.
+ OnCancel();
+}
+
+void SecurePaymentConfirmationController::CloseDialog() {
+ if (view_)
+ view_->HideDialog();
+}
+
+void SecurePaymentConfirmationController::ShowErrorMessage() {
+ // Error message is not supported.
+ OnCancel();
+}
+
+void SecurePaymentConfirmationController::ShowProcessingSpinner() {
+ if (!view_)
+ return;
+
+ model_.set_progress_bar_visible(true);
+ model_.set_verify_button_enabled(false);
+ model_.set_cancel_button_enabled(false);
+ view_->OnModelUpdated();
+}
+
+bool SecurePaymentConfirmationController::IsInteractive() const {
+ return view_ && !model_.progress_bar_visible();
+}
+
+void SecurePaymentConfirmationController::ShowCvcUnmaskPrompt(
+ const autofill::CreditCard& credit_card,
+ base::WeakPtr<autofill::payments::FullCardRequest::ResultDelegate>
+ result_delegate,
+ content::WebContents* web_contents) {
+ // CVC unmasking is nut supported.
+ NOTREACHED();
+}
+
+void SecurePaymentConfirmationController::ShowPaymentHandlerScreen(
+ const GURL& url,
+ PaymentHandlerOpenWindowCallback callback) {
+ // Payment handler screen is not supported.
+ NOTREACHED();
+}
+
+void SecurePaymentConfirmationController::ConfirmPaymentForTesting() {
+ OnConfirm();
+}
+
+void SecurePaymentConfirmationController::OnInitialized(
+ InitializationTask* initialization_task) {
+ if (--number_of_initialization_tasks_ == 0)
+ SetupModelAndShowDialogIfApplicable();
+}
+
+void SecurePaymentConfirmationController::OnDismiss() {
+ OnCancel();
+}
+
+void SecurePaymentConfirmationController::OnCancel() {
+ CloseDialog();
+
+ if (!request_)
+ return;
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&PaymentRequest::UserCancelled, request_));
+}
+
+void SecurePaymentConfirmationController::OnConfirm() {
+ if (!request_)
+ return;
+
+ ShowProcessingSpinner();
+
+ // This will trigger WebAuthn with OS-level UI (if any) on top of the |view_|
+ // with its animated processing spinner. For example, on Linux, there's no
+ // OS-level UI, while on MacOS, there's an OS-level prompt for the Touch ID
+ // that shows on top of Chrome.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&PaymentRequest::Pay, request_));
+}
+
+base::WeakPtr<SecurePaymentConfirmationController>
+SecurePaymentConfirmationController::GetWeakPtr() {
+ return weak_ptr_factory_.GetWeakPtr();
+}
+
+} // namespace payments
diff --git a/chromium/components/payments/content/secure_payment_confirmation_controller.h b/chromium/components/payments/content/secure_payment_confirmation_controller.h
new file mode 100644
index 00000000000..4917544d606
--- /dev/null
+++ b/chromium/components/payments/content/secure_payment_confirmation_controller.h
@@ -0,0 +1,80 @@
+// Copyright 2020 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_SECURE_PAYMENT_CONFIRMATION_CONTROLLER_H_
+#define COMPONENTS_PAYMENTS_CONTENT_SECURE_PAYMENT_CONFIRMATION_CONTROLLER_H_
+
+#include "base/memory/weak_ptr.h"
+#include "components/payments/content/initialization_task.h"
+#include "components/payments/content/payment_request_dialog.h"
+#include "components/payments/content/secure_payment_confirmation_model.h"
+#include "components/payments/content/secure_payment_confirmation_view.h"
+
+namespace payments {
+
+class PaymentRequest;
+
+// Controls the user interface in the secure payment confirmation flow.
+class SecurePaymentConfirmationController
+ : public PaymentRequestDialog,
+ public InitializationTask::Observer {
+ public:
+ explicit SecurePaymentConfirmationController(
+ base::WeakPtr<PaymentRequest> request);
+ ~SecurePaymentConfirmationController() override;
+
+ SecurePaymentConfirmationController(
+ const SecurePaymentConfirmationController& other) = delete;
+ SecurePaymentConfirmationController& operator=(
+ const SecurePaymentConfirmationController& other) = delete;
+
+ // PaymentRequestDialog:
+ void ShowDialog() override;
+ void RetryDialog() override;
+ void CloseDialog() override;
+ void ShowErrorMessage() override;
+ void ShowProcessingSpinner() override;
+ bool IsInteractive() const override;
+ void ShowCvcUnmaskPrompt(
+ const autofill::CreditCard& credit_card,
+ base::WeakPtr<autofill::payments::FullCardRequest::ResultDelegate>
+ result_delegate,
+ content::WebContents* web_contents) override;
+ void ShowPaymentHandlerScreen(
+ const GURL& url,
+ PaymentHandlerOpenWindowCallback callback) override;
+ void ConfirmPaymentForTesting() override;
+
+ // InitializationTask::Observer:
+ void OnInitialized(InitializationTask* initialization_task) override;
+
+ // Callbacks for user interaction.
+ void OnDismiss();
+ void OnCancel();
+ void OnConfirm();
+
+ base::WeakPtr<SecurePaymentConfirmationController> GetWeakPtr();
+
+ private:
+ void SetupModelAndShowDialogIfApplicable();
+
+ base::WeakPtr<PaymentRequest> request_;
+
+ SecurePaymentConfirmationModel model_;
+
+ // On desktop, the SecurePaymentConfirmationView object is memory managed by
+ // the views:: machinery. It is deleted when the window is closed and
+ // views::DialogDelegateView::DeleteDelegate() is called by its corresponding
+ // views::Widget.
+ base::WeakPtr<SecurePaymentConfirmationView> view_;
+
+ int number_of_initialization_tasks_ = 0;
+
+ base::WeakPtrFactory<SecurePaymentConfirmationController> weak_ptr_factory_{
+ this};
+};
+
+} // namespace payments
+
+#endif // COMPONENTS_PAYMENTS_CONTENT_SECURE_PAYMENT_CONFIRMATION_CONTROLLER_H_
diff --git a/chromium/components/payments/content/secure_payment_confirmation_model.cc b/chromium/components/payments/content/secure_payment_confirmation_model.cc
new file mode 100644
index 00000000000..416252ac059
--- /dev/null
+++ b/chromium/components/payments/content/secure_payment_confirmation_model.cc
@@ -0,0 +1,18 @@
+// Copyright 2020 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/content/secure_payment_confirmation_model.h"
+
+namespace payments {
+
+SecurePaymentConfirmationModel::SecurePaymentConfirmationModel() = default;
+
+SecurePaymentConfirmationModel::~SecurePaymentConfirmationModel() = default;
+
+base::WeakPtr<SecurePaymentConfirmationModel>
+SecurePaymentConfirmationModel::GetWeakPtr() {
+ return weak_ptr_factory_.GetWeakPtr();
+}
+
+} // namespace payments
diff --git a/chromium/components/payments/content/secure_payment_confirmation_model.h b/chromium/components/payments/content/secure_payment_confirmation_model.h
new file mode 100644
index 00000000000..ab1cba303b2
--- /dev/null
+++ b/chromium/components/payments/content/secure_payment_confirmation_model.h
@@ -0,0 +1,150 @@
+// Copyright 2020 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_SECURE_PAYMENT_CONFIRMATION_MODEL_H_
+#define COMPONENTS_PAYMENTS_CONTENT_SECURE_PAYMENT_CONFIRMATION_MODEL_H_
+
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string16.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+namespace payments {
+
+// The data model for the secure payment confirmation flow. Owned by the
+// SecurePaymentConfirmationController.
+class SecurePaymentConfirmationModel {
+ public:
+ SecurePaymentConfirmationModel();
+ ~SecurePaymentConfirmationModel();
+
+ // Disallow copy and assign.
+ SecurePaymentConfirmationModel(const SecurePaymentConfirmationModel& other) =
+ delete;
+ SecurePaymentConfirmationModel& operator=(
+ const SecurePaymentConfirmationModel& other) = delete;
+
+ // Title, e.g. "Use TouchID to verify and complete your purchase?"
+ const base::string16& title() const { return title_; }
+ void set_title(const base::string16& title) { title_ = title; }
+
+ // Label for the merchant row, e.g. "Store".
+ const base::string16& merchant_label() const { return merchant_label_; }
+ void set_merchant_label(const base::string16& merchant_label) {
+ merchant_label_ = merchant_label;
+ }
+
+ // Label for the merchant row value, e.g. "merchant.com"
+ const base::string16& merchant_value() const { return merchant_value_; }
+ void set_merchant_value(const base::string16& merchant_value) {
+ merchant_value_ = merchant_value;
+ }
+
+ // Label for the instrument row, e.g. "Payment".
+ const base::string16& instrument_label() const { return instrument_label_; }
+ void set_instrument_label(const base::string16& instrument_label) {
+ instrument_label_ = instrument_label;
+ }
+
+ // Label for the instrument row value, e.g. "Mastercard ****4444"
+ const base::string16& instrument_value() const { return instrument_value_; }
+ void set_instrument_value(const base::string16& instrument_value) {
+ instrument_value_ = instrument_value;
+ }
+
+ // Instrument icon.
+ const SkBitmap* instrument_icon() const { return instrument_icon_; }
+ void set_instrument_icon(const SkBitmap* instrument_icon) {
+ instrument_icon_ = instrument_icon;
+ }
+
+ // Label for the total row, e.g. "Total".
+ const base::string16& total_label() const { return total_label_; }
+ void set_total_label(const base::string16& total_label) {
+ total_label_ = total_label;
+ }
+
+ // Label for the total row value, e.g. "$20.00 USD"
+ const base::string16& total_value() const { return total_value_; }
+ void set_total_value(const base::string16& total_value) {
+ total_value_ = total_value;
+ }
+
+ // Label for the verify button, e.g. "Verify".
+ const base::string16& verify_button_label() const {
+ return verify_button_label_;
+ }
+ void set_verify_button_label(const base::string16& verify_button_label) {
+ verify_button_label_ = verify_button_label;
+ }
+
+ // Label for the cancel button, e.g. "Cancel".
+ const base::string16& cancel_button_label() const {
+ return cancel_button_label_;
+ }
+ void set_cancel_button_label(const base::string16& cancel_button_label) {
+ cancel_button_label_ = cancel_button_label;
+ }
+
+ // Progress bar visibility.
+ bool progress_bar_visible() const { return progress_bar_visible_; }
+ void set_progress_bar_visible(bool progress_bar_visible) {
+ progress_bar_visible_ = progress_bar_visible;
+ }
+
+ // Verify button enabled state.
+ bool verify_button_enabled() const { return verify_button_enabled_; }
+ void set_verify_button_enabled(bool verify_button_enabled) {
+ verify_button_enabled_ = verify_button_enabled;
+ }
+
+ // Verify button visibility.
+ bool verify_button_visible() const { return verify_button_visible_; }
+ void set_verify_button_visible(bool verify_button_visible) {
+ verify_button_visible_ = verify_button_visible;
+ }
+
+ // Cancel button enabled state.
+ bool cancel_button_enabled() const { return cancel_button_enabled_; }
+ void set_cancel_button_enabled(bool cancel_button_enabled) {
+ cancel_button_enabled_ = cancel_button_enabled;
+ }
+
+ // Cancel button visibility.
+ bool cancel_button_visible() const { return cancel_button_visible_; }
+ void set_cancel_button_visible(bool cancel_button_visible) {
+ cancel_button_visible_ = cancel_button_visible;
+ }
+
+ base::WeakPtr<SecurePaymentConfirmationModel> GetWeakPtr();
+
+ private:
+ base::string16 title_;
+
+ base::string16 merchant_label_;
+ base::string16 merchant_value_;
+
+ base::string16 instrument_label_;
+ base::string16 instrument_value_;
+ const SkBitmap* instrument_icon_ = nullptr;
+
+ base::string16 total_label_;
+ base::string16 total_value_;
+
+ base::string16 verify_button_label_;
+ base::string16 cancel_button_label_;
+
+ bool progress_bar_visible_ = false;
+
+ bool verify_button_enabled_ = true;
+ bool verify_button_visible_ = true;
+
+ bool cancel_button_enabled_ = true;
+ bool cancel_button_visible_ = true;
+
+ base::WeakPtrFactory<SecurePaymentConfirmationModel> weak_ptr_factory_{this};
+};
+
+} // namespace payments
+
+#endif // COMPONENTS_PAYMENTS_CONTENT_SECURE_PAYMENT_CONFIRMATION_MODEL_H_
diff --git a/chromium/components/payments/content/secure_payment_confirmation_model_unittest.cc b/chromium/components/payments/content/secure_payment_confirmation_model_unittest.cc
new file mode 100644
index 00000000000..0d4eef1433e
--- /dev/null
+++ b/chromium/components/payments/content/secure_payment_confirmation_model_unittest.cc
@@ -0,0 +1,82 @@
+// Copyright 2020 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/content/secure_payment_confirmation_model.h"
+
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/vector_icon_types.h"
+
+namespace payments {
+
+class SecurePaymentConfirmationModelTest : public testing::Test {};
+
+TEST_F(SecurePaymentConfirmationModelTest, SmokeTest) {
+ SecurePaymentConfirmationModel model;
+
+ base::string16 title(
+ base::UTF8ToUTF16("Use Touch ID to verify and complete your purchase?"));
+ base::string16 merchant_label(base::UTF8ToUTF16("Store"));
+ base::string16 merchant_value(base::UTF8ToUTF16("merchant.com"));
+ base::string16 instrument_label(base::UTF8ToUTF16("Payment"));
+ base::string16 instrument_value(base::UTF8ToUTF16("Mastercard ****4444"));
+ SkBitmap instrument_icon;
+ base::string16 total_label(base::UTF8ToUTF16("Total"));
+ base::string16 total_value(base::UTF8ToUTF16("$20.00 USD"));
+ base::string16 verify_button_label(base::UTF8ToUTF16("Verify"));
+ base::string16 cancel_button_label(base::UTF8ToUTF16("Cancel"));
+
+ model.set_title(title);
+ EXPECT_EQ(title, model.title());
+
+ model.set_merchant_label(merchant_label);
+ EXPECT_EQ(merchant_label, model.merchant_label());
+
+ model.set_merchant_value(merchant_value);
+ EXPECT_EQ(merchant_value, model.merchant_value());
+
+ model.set_instrument_label(instrument_label);
+ EXPECT_EQ(instrument_label, model.instrument_label());
+
+ model.set_instrument_value(instrument_value);
+ EXPECT_EQ(instrument_value, model.instrument_value());
+
+ model.set_instrument_icon(&instrument_icon);
+ EXPECT_EQ(&instrument_icon, model.instrument_icon());
+
+ model.set_total_label(total_label);
+ EXPECT_EQ(total_label, model.total_label());
+
+ model.set_total_value(total_value);
+ EXPECT_EQ(total_value, model.total_value());
+
+ model.set_verify_button_label(verify_button_label);
+ EXPECT_EQ(verify_button_label, model.verify_button_label());
+
+ model.set_cancel_button_label(cancel_button_label);
+ EXPECT_EQ(cancel_button_label, model.cancel_button_label());
+
+ // Default values for visibility and enabled states
+ EXPECT_FALSE(model.progress_bar_visible());
+ EXPECT_TRUE(model.verify_button_enabled());
+ EXPECT_TRUE(model.verify_button_visible());
+ EXPECT_TRUE(model.cancel_button_enabled());
+ EXPECT_TRUE(model.cancel_button_visible());
+
+ model.set_progress_bar_visible(true);
+ model.set_verify_button_enabled(false);
+ model.set_verify_button_visible(false);
+ model.set_cancel_button_enabled(false);
+ model.set_cancel_button_visible(false);
+
+ EXPECT_TRUE(model.progress_bar_visible());
+ EXPECT_FALSE(model.verify_button_enabled());
+ EXPECT_FALSE(model.verify_button_visible());
+ EXPECT_FALSE(model.cancel_button_enabled());
+ EXPECT_FALSE(model.cancel_button_visible());
+}
+
+} // namespace payments
diff --git a/chromium/components/payments/content/secure_payment_confirmation_view.h b/chromium/components/payments/content/secure_payment_confirmation_view.h
new file mode 100644
index 00000000000..ea80e2b0466
--- /dev/null
+++ b/chromium/components/payments/content/secure_payment_confirmation_view.h
@@ -0,0 +1,45 @@
+// Copyright 2020 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_SECURE_PAYMENT_CONFIRMATION_VIEW_H_
+#define COMPONENTS_PAYMENTS_CONTENT_SECURE_PAYMENT_CONFIRMATION_VIEW_H_
+
+#include "base/callback_forward.h"
+#include "base/memory/weak_ptr.h"
+
+namespace content {
+class WebContents;
+} // namespace content
+
+namespace payments {
+
+class SecurePaymentConfirmationModel;
+
+// Draws the user interface in the secure payment confirmation flow. Owned by
+// the SecurePaymentConfirmationController.
+class SecurePaymentConfirmationView {
+ public:
+ using VerifyCallback = base::OnceCallback<void()>;
+ using CancelCallback = base::OnceCallback<void()>;
+
+ static base::WeakPtr<SecurePaymentConfirmationView> Create();
+
+ virtual ~SecurePaymentConfirmationView() = 0;
+
+ virtual void ShowDialog(content::WebContents* web_contents,
+ base::WeakPtr<SecurePaymentConfirmationModel> model,
+ VerifyCallback verify_callback,
+ CancelCallback cancel_callback) = 0;
+ virtual void OnModelUpdated() = 0;
+ virtual void HideDialog() = 0;
+
+ protected:
+ SecurePaymentConfirmationView();
+
+ base::WeakPtr<SecurePaymentConfirmationModel> model_;
+};
+
+} // namespace payments
+
+#endif // COMPONENTS_PAYMENTS_CONTENT_SECURE_PAYMENT_CONFIRMATION_VIEW_H_
diff --git a/chromium/components/payments/content/secure_payment_confirmation_view_stub.cc b/chromium/components/payments/content/secure_payment_confirmation_view_stub.cc
new file mode 100644
index 00000000000..6241d62f0b0
--- /dev/null
+++ b/chromium/components/payments/content/secure_payment_confirmation_view_stub.cc
@@ -0,0 +1,15 @@
+// Copyright 2020 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/content/secure_payment_confirmation_view.h"
+
+namespace payments {
+
+// static
+base::WeakPtr<SecurePaymentConfirmationView>
+SecurePaymentConfirmationView::Create() {
+ return nullptr;
+}
+
+} // namespace payments
diff --git a/chromium/components/payments/content/service_worker_payment_app.cc b/chromium/components/payments/content/service_worker_payment_app.cc
index 6d51dcaca5d..1848cdae159 100644
--- a/chromium/components/payments/content/service_worker_payment_app.cc
+++ b/chromium/components/payments/content/service_worker_payment_app.cc
@@ -32,7 +32,7 @@ ServiceWorkerPaymentApp::ServiceWorkerPaymentApp(
content::WebContents* web_contents,
const GURL& top_origin,
const GURL& frame_origin,
- const PaymentRequestSpec* spec,
+ PaymentRequestSpec* spec,
std::unique_ptr<content::StoredPaymentApp> stored_payment_app_info,
bool is_incognito,
const base::RepeatingClosure& show_processing_spinner)
@@ -40,7 +40,7 @@ ServiceWorkerPaymentApp::ServiceWorkerPaymentApp(
content::WebContentsObserver(web_contents),
top_origin_(top_origin),
frame_origin_(frame_origin),
- spec_(spec),
+ spec_(spec->GetWeakPtr()),
stored_payment_app_info_(std::move(stored_payment_app_info)),
delegate_(nullptr),
is_incognito_(is_incognito),
@@ -51,7 +51,6 @@ ServiceWorkerPaymentApp::ServiceWorkerPaymentApp(
DCHECK(web_contents);
DCHECK(top_origin_.is_valid());
DCHECK(frame_origin_.is_valid());
- DCHECK(spec_);
app_method_names_.insert(stored_payment_app_info_->enabled_methods.begin(),
stored_payment_app_info_->enabled_methods.end());
@@ -63,7 +62,7 @@ ServiceWorkerPaymentApp::ServiceWorkerPaymentApp(
content::WebContents* web_contents,
const GURL& top_origin,
const GURL& frame_origin,
- const PaymentRequestSpec* spec,
+ PaymentRequestSpec* spec,
std::unique_ptr<WebAppInstallationInfo> installable_payment_app_info,
const std::string& enabled_method,
bool is_incognito,
@@ -72,7 +71,7 @@ ServiceWorkerPaymentApp::ServiceWorkerPaymentApp(
content::WebContentsObserver(web_contents),
top_origin_(top_origin),
frame_origin_(frame_origin),
- spec_(spec),
+ spec_(spec->GetWeakPtr()),
delegate_(nullptr),
is_incognito_(is_incognito),
show_processing_spinner_(show_processing_spinner),
@@ -84,7 +83,6 @@ ServiceWorkerPaymentApp::ServiceWorkerPaymentApp(
DCHECK(web_contents);
DCHECK(top_origin_.is_valid());
DCHECK(frame_origin_.is_valid());
- DCHECK(spec_);
app_method_names_.insert(installable_enabled_method_);
}
@@ -100,6 +98,9 @@ ServiceWorkerPaymentApp::~ServiceWorkerPaymentApp() {
void ServiceWorkerPaymentApp::ValidateCanMakePayment(
ValidateCanMakePaymentCallback callback) {
+ if (!spec_)
+ return;
+
// Returns true for payment app that needs installation.
if (needs_installation_) {
OnCanMakePaymentEventSkipped(std::move(callback));
@@ -140,6 +141,9 @@ void ServiceWorkerPaymentApp::ValidateCanMakePayment(
mojom::CanMakePaymentEventDataPtr
ServiceWorkerPaymentApp::CreateCanMakePaymentEventData() {
+ if (!spec_)
+ return nullptr;
+
std::set<std::string> requested_url_methods;
for (const auto& method : spec_->payment_method_identifiers_set()) {
GURL url_method(method);
@@ -250,6 +254,9 @@ ServiceWorkerPaymentApp::CreatePaymentRequestEventData() {
mojom::PaymentRequestEventDataPtr event_data =
mojom::PaymentRequestEventData::New();
+ if (!spec_)
+ return event_data;
+
event_data->top_origin = top_origin_;
event_data->payment_request_origin = frame_origin_;
@@ -509,7 +516,7 @@ void ServiceWorkerPaymentApp::DisableShowingOwnUI() {
}
bool ServiceWorkerPaymentApp::HandlesShippingAddress() const {
- if (!spec_->request_shipping())
+ if (!spec_ || !spec_->request_shipping())
return false;
return needs_installation_
@@ -518,7 +525,7 @@ bool ServiceWorkerPaymentApp::HandlesShippingAddress() const {
}
bool ServiceWorkerPaymentApp::HandlesPayerName() const {
- if (!spec_->request_payer_name())
+ if (!spec_ || !spec_->request_payer_name())
return false;
return needs_installation_
@@ -527,7 +534,7 @@ bool ServiceWorkerPaymentApp::HandlesPayerName() const {
}
bool ServiceWorkerPaymentApp::HandlesPayerEmail() const {
- if (!spec_->request_payer_email())
+ if (!spec_ || !spec_->request_payer_email())
return false;
return needs_installation_
@@ -536,7 +543,7 @@ bool ServiceWorkerPaymentApp::HandlesPayerEmail() const {
}
bool ServiceWorkerPaymentApp::HandlesPayerPhone() const {
- if (!spec_->request_payer_phone())
+ if (!spec_ || !spec_->request_payer_phone())
return false;
return needs_installation_
@@ -592,6 +599,9 @@ void ServiceWorkerPaymentApp::OnPaymentDetailsNotUpdated() {
void ServiceWorkerPaymentApp::AbortPaymentApp(
base::OnceCallback<void(bool)> abort_callback) {
+ if (!spec_)
+ return;
+
content::PaymentAppProvider::GetInstance()->AbortPayment(
web_contents(), registration_id_,
stored_payment_app_info_
diff --git a/chromium/components/payments/content/service_worker_payment_app.h b/chromium/components/payments/content/service_worker_payment_app.h
index a7ab79d38bf..69ada2abb92 100644
--- a/chromium/components/payments/content/service_worker_payment_app.h
+++ b/chromium/components/payments/content/service_worker_payment_app.h
@@ -36,23 +36,24 @@ class ServiceWorkerPaymentApp : public PaymentApp,
public content::WebContentsObserver {
public:
// This constructor is used for a payment app that has been installed in
- // Chrome.
+ // Chrome. The `spec` parameter should not be null.
ServiceWorkerPaymentApp(
content::WebContents* web_contents,
const GURL& top_origin,
const GURL& frame_origin,
- const PaymentRequestSpec* spec,
+ PaymentRequestSpec* spec,
std::unique_ptr<content::StoredPaymentApp> stored_payment_app_info,
bool is_incognito,
const base::RepeatingClosure& show_processing_spinner);
// This constructor is used for a payment app that has not been installed in
- // Chrome but can be installed when paying with it.
+ // Chrome but can be installed when paying with it. The `spec` parameter
+ // should not be null.
ServiceWorkerPaymentApp(
content::WebContents* web_contents,
const GURL& top_origin,
const GURL& frame_origin,
- const PaymentRequestSpec* spec,
+ PaymentRequestSpec* spec,
std::unique_ptr<WebAppInstallationInfo> installable_payment_app_info,
const std::string& enabled_method,
bool is_incognito,
@@ -131,7 +132,7 @@ class ServiceWorkerPaymentApp : public PaymentApp,
GURL top_origin_;
GURL frame_origin_;
- const PaymentRequestSpec* spec_;
+ base::WeakPtr<PaymentRequestSpec> spec_;
std::unique_ptr<content::StoredPaymentApp> stored_payment_app_info_;
// Weak pointer is fine here since the owner of this object is
diff --git a/chromium/components/payments/content/service_worker_payment_app_factory.cc b/chromium/components/payments/content/service_worker_payment_app_factory.cc
index 5b710d33c7f..963a064fb99 100644
--- a/chromium/components/payments/content/service_worker_payment_app_factory.cc
+++ b/chromium/components/payments/content/service_worker_payment_app_factory.cc
@@ -10,9 +10,15 @@
#include "base/bind.h"
#include "base/check_op.h"
#include "base/memory/weak_ptr.h"
+#include "components/payments/content/developer_console_logger.h"
#include "components/payments/content/payment_manifest_web_data_service.h"
#include "components/payments/content/service_worker_payment_app.h"
#include "components/payments/content/service_worker_payment_app_finder.h"
+#include "components/payments/core/error_message_util.h"
+#include "components/payments/core/features.h"
+#include "components/payments/core/method_strings.h"
+#include "content/public/browser/stored_payment_app.h"
+#include "content/public/browser/supported_delegations.h"
#include "content/public/browser/web_contents.h"
namespace payments {
@@ -34,15 +40,15 @@ class ServiceWorkerPaymentAppCreator {
ServiceWorkerPaymentAppCreator(
ServiceWorkerPaymentAppFactory* owner,
base::WeakPtr<PaymentAppFactory::Delegate> delegate)
- : owner_(owner), delegate_(delegate) {}
+ : owner_(owner), delegate_(delegate), log_(delegate->GetWebContents()) {}
~ServiceWorkerPaymentAppCreator() {}
void CreatePaymentApps(
- content::PaymentAppProvider::PaymentApps apps,
+ content::InstalledPaymentAppsFinder::PaymentApps apps,
ServiceWorkerPaymentAppFinder::InstallablePaymentApps installable_apps,
const std::string& error_message) {
- if (!delegate_) {
+ if (!delegate_ || !delegate_->GetSpec()) {
FinishAndCleanup();
return;
}
@@ -50,15 +56,22 @@ class ServiceWorkerPaymentAppCreator {
if (!error_message.empty())
delegate_->OnPaymentAppCreationError(error_message);
- number_of_pending_sw_payment_apps_ = apps.size() + installable_apps.size();
- if (number_of_pending_sw_payment_apps_ == 0U) {
- FinishAndCleanup();
- return;
- }
base::RepeatingClosure show_processing_spinner = base::BindRepeating(
&PaymentAppFactory::Delegate::ShowProcessingSpinner, delegate_);
-
+ std::vector<std::string> skipped_app_names;
for (auto& installed_app : apps) {
+ std::vector<std::string> enabled_methods =
+ installed_app.second->enabled_methods;
+ bool has_app_store_billing_method =
+ enabled_methods.end() != std::find(enabled_methods.begin(),
+ enabled_methods.end(),
+ methods::kGooglePlayBilling);
+ if (ShouldSkipAppForPartialDelegation(
+ installed_app.second->supported_delegations, delegate_,
+ has_app_store_billing_method)) {
+ skipped_app_names.emplace_back(installed_app.second->name);
+ continue;
+ }
auto app = std::make_unique<ServiceWorkerPaymentApp>(
delegate_->GetWebContents(), delegate_->GetTopOrigin(),
delegate_->GetFrameOrigin(), delegate_->GetSpec(),
@@ -69,9 +82,18 @@ class ServiceWorkerPaymentAppCreator {
weak_ptr_factory_.GetWeakPtr()));
PaymentApp* raw_payment_app_pointer = app.get();
available_apps_[raw_payment_app_pointer] = std::move(app);
+ number_of_pending_sw_payment_apps_++;
}
for (auto& installable_app : installable_apps) {
+ bool is_app_store_billing_method =
+ installable_app.first.spec() == methods::kGooglePlayBilling;
+ if (ShouldSkipAppForPartialDelegation(
+ installable_app.second->supported_delegations, delegate_,
+ is_app_store_billing_method)) {
+ skipped_app_names.emplace_back(installable_app.second->name);
+ continue;
+ }
auto app = std::make_unique<ServiceWorkerPaymentApp>(
delegate_->GetWebContents(), delegate_->GetTopOrigin(),
delegate_->GetFrameOrigin(), delegate_->GetSpec(),
@@ -82,7 +104,35 @@ class ServiceWorkerPaymentAppCreator {
weak_ptr_factory_.GetWeakPtr()));
PaymentApp* raw_payment_app_pointer = app.get();
available_apps_[raw_payment_app_pointer] = std::move(app);
+ number_of_pending_sw_payment_apps_++;
+ }
+
+ if (!skipped_app_names.empty()) {
+ std::string warning_message =
+ GetAppsSkippedForPartialDelegationErrorMessage(skipped_app_names);
+ log_.Warn(warning_message);
}
+
+ if (number_of_pending_sw_payment_apps_ == 0U) {
+ if (error_message.empty() && !skipped_app_names.empty()) {
+ std::string new_error_message =
+ GetAppsSkippedForPartialDelegationErrorMessage(skipped_app_names);
+ delegate_->OnPaymentAppCreationError(new_error_message);
+ }
+ FinishAndCleanup();
+ }
+ }
+
+ bool ShouldSkipAppForPartialDelegation(
+ const content::SupportedDelegations& supported_delegations,
+ const base::WeakPtr<PaymentAppFactory::Delegate>& delegate,
+ bool has_app_store_billing_method) const {
+ DCHECK(delegate);
+ DCHECK(delegate->GetSpec());
+ return (base::FeatureList::IsEnabled(features::kEnforceFullDelegation) ||
+ has_app_store_billing_method) &&
+ !supported_delegations.ProvidesAll(
+ delegate->GetSpec()->payment_options());
}
base::WeakPtr<ServiceWorkerPaymentAppCreator> GetWeakPtr() {
@@ -116,6 +166,7 @@ class ServiceWorkerPaymentAppCreator {
ServiceWorkerPaymentAppFactory* owner_;
base::WeakPtr<PaymentAppFactory::Delegate> delegate_;
std::map<PaymentApp*, std::unique_ptr<PaymentApp>> available_apps_;
+ DeveloperConsoleLogger log_;
int number_of_pending_sw_payment_apps_ = 0;
base::WeakPtrFactory<ServiceWorkerPaymentAppCreator> weak_ptr_factory_{this};
diff --git a/chromium/components/payments/content/service_worker_payment_app_finder.cc b/chromium/components/payments/content/service_worker_payment_app_finder.cc
index 87b33d0f5af..5dc754e8c97 100644
--- a/chromium/components/payments/content/service_worker_payment_app_finder.cc
+++ b/chromium/components/payments/content/service_worker_payment_app_finder.cc
@@ -31,7 +31,6 @@
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/stored_payment_app.h"
#include "content/public/browser/web_contents.h"
-#include "net/url_request/url_request_context_getter.h"
#include "ui/gfx/image/image.h"
#include "url/url_canon.h"
@@ -84,7 +83,7 @@ bool AppSupportsAtLeastOneRequestedMethodData(
}
void RemovePortNumbersFromScopesForTest(
- content::PaymentAppProvider::PaymentApps* apps) {
+ content::InstalledPaymentAppsFinder::PaymentApps* apps) {
GURL::Replacements replacements;
replacements.ClearPort();
for (auto& app : *apps) {
@@ -160,9 +159,9 @@ class SelfDeletingServiceWorkerPaymentAppFinder
finished_using_resources_callback_ =
std::move(finished_using_resources_callback);
- content::PaymentAppProvider::GetInstance()->GetAllPaymentApps(
- web_contents->GetBrowserContext(),
- base::BindOnce(
+ content::InstalledPaymentAppsFinder::GetInstance(
+ web_contents->GetBrowserContext())
+ ->GetAllPaymentApps(base::BindOnce(
&SelfDeletingServiceWorkerPaymentAppFinder::OnGotAllPaymentApps,
weak_ptr_factory_.GetWeakPtr()));
}
@@ -179,7 +178,7 @@ class SelfDeletingServiceWorkerPaymentAppFinder
static void RemoveUnrequestedMethods(
const std::vector<mojom::PaymentMethodDataPtr>& requested_method_data,
- content::PaymentAppProvider::PaymentApps* apps) {
+ content::InstalledPaymentAppsFinder::PaymentApps* apps) {
std::set<std::string> requested_methods;
for (const auto& requested_method_datum : requested_method_data) {
requested_methods.insert(requested_method_datum->supported_method);
@@ -193,7 +192,8 @@ class SelfDeletingServiceWorkerPaymentAppFinder
}
}
- void OnGotAllPaymentApps(content::PaymentAppProvider::PaymentApps apps) {
+ void OnGotAllPaymentApps(
+ content::InstalledPaymentAppsFinder::PaymentApps apps) {
if (ignore_port_in_origin_comparison_for_testing_)
RemovePortNumbersFromScopesForTest(&apps);
@@ -220,8 +220,9 @@ class SelfDeletingServiceWorkerPaymentAppFinder
weak_ptr_factory_.GetWeakPtr()));
}
- void OnPaymentAppsVerified(content::PaymentAppProvider::PaymentApps apps,
- const std::string& error_message) {
+ void OnPaymentAppsVerified(
+ content::InstalledPaymentAppsFinder::PaymentApps apps,
+ const std::string& error_message) {
if (first_error_message_.empty())
first_error_message_ = error_message;
@@ -382,7 +383,7 @@ class SelfDeletingServiceWorkerPaymentAppFinder
bool ignore_port_in_origin_comparison_for_testing_ = false;
- content::PaymentAppProvider::PaymentApps installed_apps_;
+ content::InstalledPaymentAppsFinder::PaymentApps installed_apps_;
size_t number_of_app_icons_to_update_ = 0;
@@ -410,7 +411,7 @@ void ServiceWorkerPaymentAppFinder::GetAllPaymentApps(
});
if (requested_method_data.empty()) {
std::move(callback).Run(
- content::PaymentAppProvider::PaymentApps(),
+ content::InstalledPaymentAppsFinder::PaymentApps(),
std::map<GURL, std::unique_ptr<WebAppInstallationInfo>>(),
/*error_message=*/"");
return;
@@ -444,7 +445,7 @@ void ServiceWorkerPaymentAppFinder::GetAllPaymentApps(
// static
void ServiceWorkerPaymentAppFinder::RemoveAppsWithoutMatchingMethodData(
const std::vector<mojom::PaymentMethodDataPtr>& requested_method_data,
- content::PaymentAppProvider::PaymentApps* apps) {
+ content::InstalledPaymentAppsFinder::PaymentApps* apps) {
for (auto it = apps->begin(); it != apps->end();) {
if (AppSupportsAtLeastOneRequestedMethodData(*it->second,
requested_method_data)) {
diff --git a/chromium/components/payments/content/service_worker_payment_app_finder.h b/chromium/components/payments/content/service_worker_payment_app_finder.h
index 980e4f044ed..0f8b632f866 100644
--- a/chromium/components/payments/content/service_worker_payment_app_finder.h
+++ b/chromium/components/payments/content/service_worker_payment_app_finder.h
@@ -13,6 +13,7 @@
#include "base/callback.h"
#include "base/macros.h"
#include "components/payments/content/web_app_manifest.h"
+#include "content/public/browser/installed_payment_apps_finder.h"
#include "content/public/browser/payment_app_provider.h"
#include "content/public/browser/render_document_host_user_data.h"
#include "third_party/blink/public/mojom/payments/payment_request.mojom.h"
@@ -43,7 +44,7 @@ class ServiceWorkerPaymentAppFinder
using InstallablePaymentApps =
std::map<GURL, std::unique_ptr<WebAppInstallationInfo>>;
using GetAllPaymentAppsCallback =
- base::OnceCallback<void(content::PaymentAppProvider::PaymentApps,
+ base::OnceCallback<void(content::InstalledPaymentAppsFinder::PaymentApps,
InstallablePaymentApps,
const std::string& error_message)>;
@@ -78,7 +79,7 @@ class ServiceWorkerPaymentAppFinder
// the method names and method-specific capabilities.
static void RemoveAppsWithoutMatchingMethodData(
const std::vector<mojom::PaymentMethodDataPtr>& requested_method_data,
- content::PaymentAppProvider::PaymentApps* apps);
+ content::InstalledPaymentAppsFinder::PaymentApps* apps);
// Ignore the given |method|, so that no installed or installable service
// workers would ever be looked up in GetAllPaymentApps(). Calling this
@@ -93,6 +94,7 @@ class ServiceWorkerPaymentAppFinder
friend class ServiceWorkerPaymentAppFinderBrowserTest;
friend class PaymentRequestPlatformBrowserTestBase;
friend class PaymentMethodViewControllerTest;
+ friend class PaymentHandlerIconRefetchTest;
explicit ServiceWorkerPaymentAppFinder(content::RenderFrameHost* rfh);
diff --git a/chromium/components/payments/content/service_worker_payment_app_finder_unittest.cc b/chromium/components/payments/content/service_worker_payment_app_finder_unittest.cc
index 32ab27500ca..feb18459886 100644
--- a/chromium/components/payments/content/service_worker_payment_app_finder_unittest.cc
+++ b/chromium/components/payments/content/service_worker_payment_app_finder_unittest.cc
@@ -13,7 +13,7 @@ class ServiceWorkerPaymentAppFinderTest : public testing::Test {
protected:
void RemoveAppsWithoutMatchingMethodData(
const std::vector<mojom::PaymentMethodDataPtr>& requested_method_data,
- content::PaymentAppProvider::PaymentApps* apps) {
+ content::InstalledPaymentAppsFinder::PaymentApps* apps) {
ServiceWorkerPaymentAppFinder::RemoveAppsWithoutMatchingMethodData(
requested_method_data, apps);
}
@@ -24,7 +24,7 @@ TEST_F(ServiceWorkerPaymentAppFinderTest,
std::vector<mojom::PaymentMethodDataPtr> requested_methods;
requested_methods.emplace_back(mojom::PaymentMethodData::New());
requested_methods.back()->supported_method = "method";
- content::PaymentAppProvider::PaymentApps no_apps;
+ content::InstalledPaymentAppsFinder::PaymentApps no_apps;
RemoveAppsWithoutMatchingMethodData(requested_methods, &no_apps);
@@ -34,7 +34,7 @@ TEST_F(ServiceWorkerPaymentAppFinderTest,
TEST_F(ServiceWorkerPaymentAppFinderTest,
RemoveAppsWithoutMatchingMethodData_NoMethods) {
std::vector<mojom::PaymentMethodDataPtr> no_requested_methods;
- content::PaymentAppProvider::PaymentApps apps;
+ content::InstalledPaymentAppsFinder::PaymentApps apps;
apps[0] = std::make_unique<content::StoredPaymentApp>();
apps[0]->enabled_methods = {"method1", "method2"};
@@ -52,7 +52,7 @@ TEST_F(ServiceWorkerPaymentAppFinderTest,
requested_methods.back()->supported_method = "method2";
requested_methods.emplace_back(mojom::PaymentMethodData::New());
requested_methods.back()->supported_method = "method3";
- content::PaymentAppProvider::PaymentApps apps;
+ content::InstalledPaymentAppsFinder::PaymentApps apps;
apps[0] = std::make_unique<content::StoredPaymentApp>();
apps[0]->enabled_methods = {"method2"};
apps[1] = std::make_unique<content::StoredPaymentApp>();
@@ -76,7 +76,7 @@ TEST_F(ServiceWorkerPaymentAppFinderTest,
std::vector<mojom::PaymentMethodDataPtr> requested_methods;
requested_methods.emplace_back(mojom::PaymentMethodData::New());
requested_methods.back()->supported_method = "basic-card";
- content::PaymentAppProvider::PaymentApps apps;
+ content::InstalledPaymentAppsFinder::PaymentApps apps;
apps[0] = std::make_unique<content::StoredPaymentApp>();
apps[0]->enabled_methods = {"basic-card"};
@@ -95,7 +95,7 @@ TEST_F(ServiceWorkerPaymentAppFinderTest,
requested_methods.back()->supported_method = "basic-card";
requested_methods.back()->supported_networks = {
mojom::BasicCardNetwork::AMEX};
- content::PaymentAppProvider::PaymentApps apps;
+ content::InstalledPaymentAppsFinder::PaymentApps apps;
apps[0] = std::make_unique<content::StoredPaymentApp>();
apps[0]->enabled_methods = {"basic-card"};
@@ -111,7 +111,7 @@ TEST_F(ServiceWorkerPaymentAppFinderTest,
requested_methods.back()->supported_method = "basic-card";
requested_methods.back()->supported_networks = {
mojom::BasicCardNetwork::AMEX};
- content::PaymentAppProvider::PaymentApps apps;
+ content::InstalledPaymentAppsFinder::PaymentApps apps;
apps[0] = std::make_unique<content::StoredPaymentApp>();
apps[0]->enabled_methods = {"basic-card"};
apps[0]->capabilities.emplace_back();
@@ -128,7 +128,7 @@ TEST_F(ServiceWorkerPaymentAppFinderTest,
std::vector<mojom::PaymentMethodDataPtr> requested_methods;
requested_methods.emplace_back(mojom::PaymentMethodData::New());
requested_methods.back()->supported_method = "basic-card";
- content::PaymentAppProvider::PaymentApps apps;
+ content::InstalledPaymentAppsFinder::PaymentApps apps;
apps[0] = std::make_unique<content::StoredPaymentApp>();
apps[0]->enabled_methods = {"basic-card"};
apps[0]->capabilities.emplace_back();
@@ -155,7 +155,7 @@ TEST_F(ServiceWorkerPaymentAppFinderTest,
requested_methods.back()->supported_method = "basic-card";
requested_methods.back()->supported_networks = {
mojom::BasicCardNetwork::AMEX, mojom::BasicCardNetwork::DINERS};
- content::PaymentAppProvider::PaymentApps apps;
+ content::InstalledPaymentAppsFinder::PaymentApps apps;
apps[0] = std::make_unique<content::StoredPaymentApp>();
apps[0]->enabled_methods = {"basic-card"};
apps[0]->capabilities.emplace_back();
@@ -182,7 +182,7 @@ TEST_F(ServiceWorkerPaymentAppFinderTest,
std::vector<mojom::PaymentMethodDataPtr> requested_methods;
requested_methods.emplace_back(mojom::PaymentMethodData::New());
requested_methods.back()->supported_method = "unknown-method";
- content::PaymentAppProvider::PaymentApps apps;
+ content::InstalledPaymentAppsFinder::PaymentApps apps;
apps[0] = std::make_unique<content::StoredPaymentApp>();
apps[0]->enabled_methods = {"unknown-method"};
apps[0]->capabilities.emplace_back();
diff --git a/chromium/components/payments/content/test_content_payment_request_delegate.cc b/chromium/components/payments/content/test_content_payment_request_delegate.cc
index 281f95c2a40..beac6fd3800 100644
--- a/chromium/components/payments/content/test_content_payment_request_delegate.cc
+++ b/chromium/components/payments/content/test_content_payment_request_delegate.cc
@@ -15,6 +15,11 @@ TestContentPaymentRequestDelegate::TestContentPaymentRequestDelegate(
TestContentPaymentRequestDelegate::~TestContentPaymentRequestDelegate() {}
+std::unique_ptr<autofill::InternalAuthenticator>
+TestContentPaymentRequestDelegate::CreateInternalAuthenticator() const {
+ return nullptr;
+}
+
scoped_refptr<PaymentManifestWebDataService>
TestContentPaymentRequestDelegate::GetPaymentManifestWebDataService() const {
return nullptr;
@@ -57,6 +62,10 @@ std::string TestContentPaymentRequestDelegate::GetTwaPackageName() const {
return "";
}
+PaymentRequestDialog* TestContentPaymentRequestDelegate::GetDialogForTesting() {
+ return nullptr;
+}
+
autofill::PersonalDataManager*
TestContentPaymentRequestDelegate::GetPersonalDataManager() {
return core_delegate_.GetPersonalDataManager();
diff --git a/chromium/components/payments/content/test_content_payment_request_delegate.h b/chromium/components/payments/content/test_content_payment_request_delegate.h
index 4cf7dceda3f..56101135d6b 100644
--- a/chromium/components/payments/content/test_content_payment_request_delegate.h
+++ b/chromium/components/payments/content/test_content_payment_request_delegate.h
@@ -22,6 +22,8 @@ class TestContentPaymentRequestDelegate : public ContentPaymentRequestDelegate {
~TestContentPaymentRequestDelegate() override;
// ContentPaymentRequestDelegate:
+ std::unique_ptr<autofill::InternalAuthenticator> CreateInternalAuthenticator()
+ const override;
scoped_refptr<PaymentManifestWebDataService>
GetPaymentManifestWebDataService() const override;
PaymentRequestDisplayManager* GetDisplayManager() override;
@@ -33,6 +35,7 @@ class TestContentPaymentRequestDelegate : public ContentPaymentRequestDelegate {
bool IsBrowserWindowActive() const override;
bool SkipUiForBasicCard() const override;
std::string GetTwaPackageName() const override;
+ PaymentRequestDialog* GetDialogForTesting() override;
autofill::PersonalDataManager* GetPersonalDataManager() override;
const std::string& GetApplicationLocale() const override;
bool IsOffTheRecord() const override;
diff --git a/chromium/components/payments/core/BUILD.gn b/chromium/components/payments/core/BUILD.gn
index 5e5e871fe3b..1f6d6e3976d 100644
--- a/chromium/components/payments/core/BUILD.gn
+++ b/chromium/components/payments/core/BUILD.gn
@@ -2,10 +2,12 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-import("//build/config/jumbo.gni")
-
-jumbo_static_library("core") {
+static_library("core") {
sources = [
+ "android_app_description.cc",
+ "android_app_description.h",
+ "android_app_description_tools.cc",
+ "android_app_description_tools.h",
"autofill_card_validation.cc",
"autofill_card_validation.h",
"basic_card_response.cc",
@@ -53,6 +55,8 @@ jumbo_static_library("core") {
"payments_experimental_features.h",
"payments_validators.cc",
"payments_validators.h",
+ "secure_payment_confirmation_instrument.cc",
+ "secure_payment_confirmation_instrument.h",
"url_util.cc",
"url_util.h",
]
@@ -99,23 +103,30 @@ jumbo_static_library("core") {
]
}
-jumbo_static_library("error_strings") {
+static_library("error_strings") {
sources = [
"error_strings.cc",
"error_strings.h",
"native_error_strings.cc",
"native_error_strings.h",
]
+
+ if (is_chromeos) {
+ sources += [
+ "chrome_os_error_strings.cc",
+ "chrome_os_error_strings.h",
+ ]
+ }
}
-jumbo_static_library("method_strings") {
+static_library("method_strings") {
sources = [
"method_strings.cc",
"method_strings.h",
]
}
-jumbo_static_library("test_support") {
+static_library("test_support") {
testonly = true
sources = [
"payments_test_util.cc",
@@ -141,9 +152,10 @@ jumbo_static_library("test_support") {
]
}
-jumbo_source_set("unit_tests") {
+source_set("unit_tests") {
testonly = true
sources = [
+ "android_app_description_tools_unittest.cc",
"can_make_payment_query_unittest.cc",
"currency_formatter_unittest.cc",
"journey_logger_unittest.cc",
diff --git a/chromium/components/payments/core/android_app_description.cc b/chromium/components/payments/core/android_app_description.cc
new file mode 100644
index 00000000000..abe6ba3cb6e
--- /dev/null
+++ b/chromium/components/payments/core/android_app_description.cc
@@ -0,0 +1,17 @@
+// Copyright 2020 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/android_app_description.h"
+
+namespace payments {
+
+AndroidActivityDescription::AndroidActivityDescription() = default;
+
+AndroidActivityDescription::~AndroidActivityDescription() = default;
+
+AndroidAppDescription::AndroidAppDescription() = default;
+
+AndroidAppDescription::~AndroidAppDescription() = default;
+
+} // namespace payments
diff --git a/chromium/components/payments/core/android_app_description.h b/chromium/components/payments/core/android_app_description.h
new file mode 100644
index 00000000000..4274dba9a77
--- /dev/null
+++ b/chromium/components/payments/core/android_app_description.h
@@ -0,0 +1,65 @@
+// Copyright 2020 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_ANDROID_APP_DESCRIPTION_H_
+#define COMPONENTS_PAYMENTS_CORE_ANDROID_APP_DESCRIPTION_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace payments {
+
+// Describes an Android activity with org.chromium.intent.action.PAY intent
+// filter. Documentation:
+// https://web.dev/android-payment-apps-overview/
+struct AndroidActivityDescription {
+ AndroidActivityDescription();
+ ~AndroidActivityDescription();
+
+ // Disallow copy and assign.
+ AndroidActivityDescription(const AndroidActivityDescription& other) = delete;
+ AndroidActivityDescription& operator=(
+ const AndroidActivityDescription& other) = delete;
+
+ // The name of the activity, e.g., "com.example.app.PaymentActivity".
+ std::string name;
+
+ // The payment method identifier from the
+ // "org.chromium.default_payment_method_name" metadata value of this activity.
+ // For example, "https://example.com/web-pay".
+ //
+ // The metadata value of "org.chromium.payment_method_names" is not yet used
+ // here, so it's omitted from the struct at this time.
+ std::string default_payment_method;
+};
+
+// Describes an Android app that can handle payments.
+struct AndroidAppDescription {
+ AndroidAppDescription();
+ ~AndroidAppDescription();
+
+ // Disallow copy and assign.
+ AndroidAppDescription(const AndroidAppDescription& other) = delete;
+ AndroidAppDescription& operator=(const AndroidAppDescription& other) = delete;
+
+ // The name of the Android package of this app, e.g., "com.example.app".
+ std::string package;
+
+ // The list of activities with org.chromium.intent.action.PAY intent filters
+ // in this app.
+ std::vector<std::unique_ptr<AndroidActivityDescription>> activities;
+
+ // The list of service names with org.chromium.intent.action.IS_READY_TO_PAY
+ // intent filters in this app. For example,
+ // ["com.example.app.IsReadyToPayService"].
+ //
+ // Note that it's a mistake to declare multiple IS_READY_TO_PAY services in an
+ // app. This mistake would be reported to the developer.
+ std::vector<std::string> service_names;
+};
+
+} // namespace payments
+
+#endif // COMPONENTS_PAYMENTS_CORE_ANDROID_APP_DESCRIPTION_H_
diff --git a/chromium/components/payments/core/android_app_description_tools.cc b/chromium/components/payments/core/android_app_description_tools.cc
new file mode 100644
index 00000000000..7183601d490
--- /dev/null
+++ b/chromium/components/payments/core/android_app_description_tools.cc
@@ -0,0 +1,32 @@
+// Copyright 2020 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/android_app_description_tools.h"
+
+#include <utility>
+
+#include "base/check.h"
+#include "components/payments/core/android_app_description.h"
+
+namespace payments {
+
+void SplitPotentiallyMultipleActivities(
+ std::unique_ptr<AndroidAppDescription> app,
+ std::vector<std::unique_ptr<AndroidAppDescription>>* destination) {
+ DCHECK(destination);
+ if (app->activities.empty())
+ return;
+ destination->emplace_back(std::move(app));
+ for (size_t i = 1; i < destination->front()->activities.size(); ++i) {
+ auto single_activity_app = std::make_unique<AndroidAppDescription>();
+ single_activity_app->package = destination->front()->package;
+ single_activity_app->service_names = destination->front()->service_names;
+ single_activity_app->activities.emplace_back(
+ std::move(destination->front()->activities[i]));
+ destination->emplace_back(std::move(single_activity_app));
+ }
+ destination->front()->activities.resize(1);
+}
+
+} // namespace payments
diff --git a/chromium/components/payments/core/android_app_description_tools.h b/chromium/components/payments/core/android_app_description_tools.h
new file mode 100644
index 00000000000..53036905aac
--- /dev/null
+++ b/chromium/components/payments/core/android_app_description_tools.h
@@ -0,0 +1,29 @@
+// Copyright 2020 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_ANDROID_APP_DESCRIPTION_TOOLS_H_
+#define COMPONENTS_PAYMENTS_CORE_ANDROID_APP_DESCRIPTION_TOOLS_H_
+
+#include <memory>
+#include <vector>
+
+namespace payments {
+
+struct AndroidAppDescription;
+
+// Moves each activity in the given |app| into its own AndroidAppDescription in
+// |destination|, so the code can treat each PAY intent target as its own
+// payment app.
+//
+// The function does not clear |destination|, so the results of multiple calls
+// can be appended to the same |destination|, e.g., in a loop.
+//
+// The |destination| should not be null.
+void SplitPotentiallyMultipleActivities(
+ std::unique_ptr<AndroidAppDescription> app,
+ std::vector<std::unique_ptr<AndroidAppDescription>>* destination);
+
+} // namespace payments
+
+#endif // COMPONENTS_PAYMENTS_CORE_ANDROID_APP_DESCRIPTION_TOOLS_H_
diff --git a/chromium/components/payments/core/android_app_description_tools_unittest.cc b/chromium/components/payments/core/android_app_description_tools_unittest.cc
new file mode 100644
index 00000000000..e7697733d70
--- /dev/null
+++ b/chromium/components/payments/core/android_app_description_tools_unittest.cc
@@ -0,0 +1,85 @@
+// Copyright 2020 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/android_app_description_tools.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "components/payments/core/android_app_description.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace payments {
+namespace {
+
+// Absence of activities should result in zero apps.
+TEST(AndroidAppDescriptionToolsTest, SplitNoActivities) {
+ std::vector<std::unique_ptr<AndroidAppDescription>> destination;
+ SplitPotentiallyMultipleActivities(std::make_unique<AndroidAppDescription>(),
+ &destination);
+ EXPECT_TRUE(destination.empty());
+}
+
+// One activity should result in one app.
+TEST(AndroidAppDescriptionToolsTest, SplitOneActivity) {
+ std::vector<std::unique_ptr<AndroidAppDescription>> destination;
+ auto app = std::make_unique<AndroidAppDescription>();
+ app->package = "com.example.app";
+ app->service_names = {"com.example.app.Service"};
+ app->activities.emplace_back(std::make_unique<AndroidActivityDescription>());
+ app->activities.back()->name = "com.example.app.Activity";
+ app->activities.back()->default_payment_method = "https://example.test";
+
+ SplitPotentiallyMultipleActivities(std::move(app), &destination);
+
+ ASSERT_EQ(1U, destination.size());
+ EXPECT_EQ("com.example.app", destination.back()->package);
+ EXPECT_EQ(std::vector<std::string>{"com.example.app.Service"},
+ destination.back()->service_names);
+ ASSERT_EQ(1U, destination.back()->activities.size());
+ EXPECT_EQ("com.example.app.Activity",
+ destination.back()->activities.back()->name);
+ EXPECT_EQ("https://example.test",
+ destination.back()->activities.back()->default_payment_method);
+}
+
+// Two activities should result in two apps.
+TEST(AndroidAppDescriptionToolsTest, SplitTwoActivities) {
+ std::vector<std::unique_ptr<AndroidAppDescription>> destination;
+ auto app = std::make_unique<AndroidAppDescription>();
+ app->package = "com.example.app";
+ app->service_names = {"com.example.app.Service"};
+ app->activities.emplace_back(std::make_unique<AndroidActivityDescription>());
+ app->activities.back()->name = "com.example.app.ActivityOne";
+ app->activities.back()->default_payment_method = "https://one.test";
+ app->activities.emplace_back(std::make_unique<AndroidActivityDescription>());
+ app->activities.back()->name = "com.example.app.ActivityTwo";
+ app->activities.back()->default_payment_method = "https://two.test";
+
+ SplitPotentiallyMultipleActivities(std::move(app), &destination);
+
+ ASSERT_EQ(2U, destination.size());
+
+ EXPECT_EQ("com.example.app", destination.front()->package);
+ EXPECT_EQ(std::vector<std::string>{"com.example.app.Service"},
+ destination.front()->service_names);
+ ASSERT_EQ(1U, destination.front()->activities.size());
+ EXPECT_EQ("com.example.app.ActivityOne",
+ destination.front()->activities.back()->name);
+ EXPECT_EQ("https://one.test",
+ destination.front()->activities.back()->default_payment_method);
+
+ EXPECT_EQ("com.example.app", destination.back()->package);
+ EXPECT_EQ(std::vector<std::string>{"com.example.app.Service"},
+ destination.back()->service_names);
+ ASSERT_EQ(1U, destination.back()->activities.size());
+ EXPECT_EQ("com.example.app.ActivityTwo",
+ destination.back()->activities.back()->name);
+ EXPECT_EQ("https://two.test",
+ destination.back()->activities.back()->default_payment_method);
+}
+
+} // namespace
+} // namespace payments
diff --git a/chromium/components/payments/core/can_make_payment_query.cc b/chromium/components/payments/core/can_make_payment_query.cc
index 17c4c6ea78a..1f955334ae7 100644
--- a/chromium/components/payments/core/can_make_payment_query.cc
+++ b/chromium/components/payments/core/can_make_payment_query.cc
@@ -24,64 +24,6 @@ CanMakePaymentQuery::~CanMakePaymentQuery() {}
bool CanMakePaymentQuery::CanQuery(
const GURL& top_level_origin,
const GURL& frame_origin,
- const std::map<std::string, std::set<std::string>>& query,
- bool per_method_quota) {
- // Check both with and without per-method quota, so that both queries are
- // recorded in case if two different tabs of the same website run with and
- // without the origin trial.
- bool can_query_with_per_method_quota =
- CanQueryWithPerMethodQuota(top_level_origin, frame_origin, query);
-
- bool can_query_without_per_method_quota =
- CanQueryWithoutPerMethodQuota(top_level_origin, frame_origin, query);
-
- if (per_method_quota ||
- base::FeatureList::IsEnabled(
- features::kWebPaymentsPerMethodCanMakePaymentQuota)) {
- return can_query_with_per_method_quota;
- }
-
- return can_query_without_per_method_quota;
-}
-
-void CanMakePaymentQuery::Shutdown() {
- // OneShotTimer cancels the timer when it is destroyed.
- timers_.clear();
-}
-
-bool CanMakePaymentQuery::CanQueryWithPerMethodQuota(
- const GURL& top_level_origin,
- const GURL& frame_origin,
- const std::map<std::string, std::set<std::string>>& query) {
- bool can_query = true;
- for (const auto& method_and_params : query) {
- std::string method = method_and_params.first;
- std::set<std::string> params = method_and_params.second;
-
- const std::string id =
- frame_origin.spec() + ":" + top_level_origin.spec() + ":" + method;
-
- auto it = per_method_queries_.find(id);
- if (it == per_method_queries_.end()) {
- auto timer = std::make_unique<base::OneShotTimer>();
- timer->Start(FROM_HERE, base::TimeDelta::FromMinutes(30),
- base::BindOnce(
- &CanMakePaymentQuery::ExpireQuotaForFrameOriginAndMethod,
- weak_ptr_factory_.GetWeakPtr(), id));
- timers_.insert(std::make_pair(id, std::move(timer)));
- per_method_queries_.insert(std::make_pair(id, params));
- continue;
- }
-
- can_query &= it->second == params;
- }
-
- return can_query;
-}
-
-bool CanMakePaymentQuery::CanQueryWithoutPerMethodQuota(
- const GURL& top_level_origin,
- const GURL& frame_origin,
const std::map<std::string, std::set<std::string>>& query) {
const std::string id = frame_origin.spec() + ":" + top_level_origin.spec();
@@ -99,15 +41,14 @@ bool CanMakePaymentQuery::CanQueryWithoutPerMethodQuota(
return it->second == query;
}
-void CanMakePaymentQuery::ExpireQuotaForFrameOrigin(const std::string& id) {
- timers_.erase(id);
- queries_.erase(id);
+void CanMakePaymentQuery::Shutdown() {
+ // OneShotTimer cancels the timer when it is destroyed.
+ timers_.clear();
}
-void CanMakePaymentQuery::ExpireQuotaForFrameOriginAndMethod(
- const std::string& id) {
+void CanMakePaymentQuery::ExpireQuotaForFrameOrigin(const std::string& id) {
timers_.erase(id);
- per_method_queries_.erase(id);
+ queries_.erase(id);
}
} // namespace payments
diff --git a/chromium/components/payments/core/can_make_payment_query.h b/chromium/components/payments/core/can_make_payment_query.h
index 519baedf820..05171abb72d 100644
--- a/chromium/components/payments/core/can_make_payment_query.h
+++ b/chromium/components/payments/core/can_make_payment_query.h
@@ -43,23 +43,13 @@ class CanMakePaymentQuery : public KeyedService {
// also allowed.
bool CanQuery(const GURL& top_level_origin,
const GURL& frame_origin,
- const std::map<std::string, std::set<std::string>>& query,
- bool per_method_quota);
+ const std::map<std::string, std::set<std::string>>& query);
// KeyedService implementation.
void Shutdown() override;
private:
- bool CanQueryWithPerMethodQuota(
- const GURL& top_level_origin,
- const GURL& frame_origin,
- const std::map<std::string, std::set<std::string>>& query);
- bool CanQueryWithoutPerMethodQuota(
- const GURL& top_level_origin,
- const GURL& frame_origin,
- const std::map<std::string, std::set<std::string>>& query);
void ExpireQuotaForFrameOrigin(const std::string& id);
- void ExpireQuotaForFrameOriginAndMethod(const std::string& id);
// A mapping of an identififer to the timer that, when fired, allows the frame
// to invoke canMakePayment() with the same identifier again.
@@ -70,10 +60,6 @@ class CanMakePaymentQuery : public KeyedService {
// JSON-stringified payment method data.
std::map<std::string, std::map<std::string, std::set<std::string>>> queries_;
- // A mapping of frame origin, top level origin, and payment method identifier
- // to the last query in the form of JSON-stringified payment method data.
- std::map<std::string, std::set<std::string>> per_method_queries_;
-
base::WeakPtrFactory<CanMakePaymentQuery> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(CanMakePaymentQuery);
diff --git a/chromium/components/payments/core/can_make_payment_query_unittest.cc b/chromium/components/payments/core/can_make_payment_query_unittest.cc
index 87a72b90b62..73765e46e0f 100644
--- a/chromium/components/payments/core/can_make_payment_query_unittest.cc
+++ b/chromium/components/payments/core/can_make_payment_query_unittest.cc
@@ -25,12 +25,10 @@ TEST_F(CanMakePaymentQueryTest,
SameHttpsOriginCannotQueryTwoDifferentCardNetworks) {
EXPECT_TRUE(
guard_.CanQuery(GURL("https://example.com"), GURL("https://example.com"),
- {{"basic-card", {"{supportedNetworks: ['amex']}"}}},
- /*per_method_quota=*/true));
+ {{"basic-card", {"{supportedNetworks: ['amex']}"}}}));
EXPECT_FALSE(
guard_.CanQuery(GURL("https://example.com"), GURL("https://example.com"),
- {{"basic-card", {"{supportedNetworks: ['visa']}"}}},
- /*per_method_quota=*/true));
+ {{"basic-card", {"{supportedNetworks: ['visa']}"}}}));
}
// A localhost website is not allowed to query all of the networks of the cards
@@ -39,12 +37,10 @@ TEST_F(CanMakePaymentQueryTest,
SameLocalhostOriginCannotQueryTwoDifferentCardNetworks) {
EXPECT_TRUE(guard_.CanQuery(
GURL("http://localhost:8080"), GURL("http://localhost:8080"),
- {{"basic-card", {"{supportedNetworks: ['amex']}"}}},
- /*per_method_quota=*/true));
+ {{"basic-card", {"{supportedNetworks: ['amex']}"}}}));
EXPECT_FALSE(guard_.CanQuery(
GURL("http://localhost:8080"), GURL("http://localhost:8080"),
- {{"basic-card", {"{supportedNetworks: ['visa']}"}}},
- /*per_method_quota=*/true));
+ {{"basic-card", {"{supportedNetworks: ['visa']}"}}}));
}
// A file website is not allowed to query all of the networks of the cards in
@@ -53,12 +49,10 @@ TEST_F(CanMakePaymentQueryTest,
SameFileOriginCannotQueryTwoDifferentCardNetworks) {
EXPECT_TRUE(guard_.CanQuery(
GURL("file:///tmp/test.html"), GURL("file:///tmp/test.html"),
- {{"basic-card", {"{supportedNetworks: ['amex']}"}}},
- /*per_method_quota=*/true));
+ {{"basic-card", {"{supportedNetworks: ['amex']}"}}}));
EXPECT_FALSE(guard_.CanQuery(
GURL("file:///tmp/test.html"), GURL("file:///tmp/test.html"),
- {{"basic-card", {"{supportedNetworks: ['visa']}"}}},
- /*per_method_quota=*/true));
+ {{"basic-card", {"{supportedNetworks: ['visa']}"}}}));
}
// Different HTTPS websites are allowed to query different card networks in
@@ -67,12 +61,10 @@ TEST_F(CanMakePaymentQueryTest,
DifferentHttpsOriginsCanQueryTwoDifferentCardNetworks) {
EXPECT_TRUE(
guard_.CanQuery(GURL("https://example.com"), GURL("https://example.com"),
- {{"basic-card", {"{supportedNetworks: ['amex']}"}}},
- /*per_method_quota=*/true));
+ {{"basic-card", {"{supportedNetworks: ['amex']}"}}}));
EXPECT_TRUE(guard_.CanQuery(
GURL("https://not-example.com"), GURL("https://not-example.com"),
- {{"basic-card", {"{supportedNetworks: ['visa']}"}}},
- /*per_method_quota=*/true));
+ {{"basic-card", {"{supportedNetworks: ['visa']}"}}}));
}
// Different localhost websites are allowed to query different card networks in
@@ -81,12 +73,10 @@ TEST_F(CanMakePaymentQueryTest,
DifferentLocalhostOriginsCanQueryTwoDifferentCardNetworks) {
EXPECT_TRUE(guard_.CanQuery(
GURL("http://localhost:8080"), GURL("http://localhost:8080"),
- {{"basic-card", {"{supportedNetworks: ['amex']}"}}},
- /*per_method_quota=*/true));
+ {{"basic-card", {"{supportedNetworks: ['amex']}"}}}));
EXPECT_TRUE(guard_.CanQuery(
GURL("http://localhost:9090"), GURL("http://localhost:9090"),
- {{"basic-card", {"{supportedNetworks: ['visa']}"}}},
- /*per_method_quota=*/true));
+ {{"basic-card", {"{supportedNetworks: ['visa']}"}}}));
}
// Different file websites are allowed to query different card networks in
@@ -95,12 +85,10 @@ TEST_F(CanMakePaymentQueryTest,
DifferentFileOriginsCanQueryTwoDifferentCardNetworks) {
EXPECT_TRUE(guard_.CanQuery(
GURL("file:///tmp/test.html"), GURL("file:///tmp/test.html"),
- {{"basic-card", {"{supportedNetworks: ['amex']}"}}},
- /*per_method_quota=*/true));
+ {{"basic-card", {"{supportedNetworks: ['amex']}"}}}));
EXPECT_TRUE(guard_.CanQuery(
GURL("file:///tmp/not-test.html"), GURL("file:///tmp/not-test.html"),
- {{"basic-card", {"{supportedNetworks: ['visa']}"}}},
- /*per_method_quota=*/true));
+ {{"basic-card", {"{supportedNetworks: ['visa']}"}}}));
}
// The same website is not allowed to query the same payment method with
@@ -110,18 +98,15 @@ TEST_F(CanMakePaymentQueryTest,
EXPECT_TRUE(
guard_.CanQuery(GURL("https://example.com"), GURL("https://example.com"),
{{"basic-card", {"{supportedNetworks: ['visa']}"}},
- {"https://alicepay.com", {"{alicePayParameter: 1}"}}},
- /*per_method_quota=*/true));
- EXPECT_TRUE(
+ {"https://alicepay.com", {"{alicePayParameter: 1}"}}}));
+ EXPECT_FALSE(
guard_.CanQuery(GURL("https://example.com"), GURL("https://example.com"),
{{"basic-card", {"{supportedNetworks: ['visa']}"}},
- {"https://bobpay.com", {"{bobPayParameter: 2}"}}},
- /*per_method_quota=*/true));
+ {"https://bobpay.com", {"{bobPayParameter: 2}"}}}));
EXPECT_FALSE(
guard_.CanQuery(GURL("https://example.com"), GURL("https://example.com"),
{{"basic-card", {"{supportedNetworks: ['amex']}"}},
- {"https://bobpay.com", {"{bobPayParameter: 2}"}}},
- /*per_method_quota=*/true));
+ {"https://bobpay.com", {"{bobPayParameter: 2}"}}}));
}
// Two different websites are allowed to query the same payment method with
@@ -130,12 +115,10 @@ TEST_F(CanMakePaymentQueryTest,
DifferentOriginsCanQueryBasicCardWithTwoDifferentCardNetworks) {
EXPECT_TRUE(
guard_.CanQuery(GURL("https://example.com"), GURL("https://example.com"),
- {{"basic-card", {"{supportedNetworks: ['visa']}"}}},
- /*per_method_quota=*/true));
+ {{"basic-card", {"{supportedNetworks: ['visa']}"}}}));
EXPECT_TRUE(guard_.CanQuery(
GURL("https://not-example.com"), GURL("https://not-example.com"),
- {{"basic-card", {"{supportedNetworks: ['amex']}"}}},
- /*per_method_quota=*/true));
+ {{"basic-card", {"{supportedNetworks: ['amex']}"}}}));
}
// A website can query several different payment methods, as long as each
@@ -145,18 +128,15 @@ TEST_F(CanMakePaymentQueryTest,
EXPECT_TRUE(
guard_.CanQuery(GURL("https://example.com"), GURL("https://example.com"),
{{"basic-card", {"{supportedNetworks: ['visa']}"}},
- {"https://alicepay.com", {"{alicePayParameter: 1}"}}},
- /*per_method_quota=*/true));
- EXPECT_TRUE(
+ {"https://alicepay.com", {"{alicePayParameter: 1}"}}}));
+ EXPECT_FALSE(
guard_.CanQuery(GURL("https://example.com"), GURL("https://example.com"),
{{"https://alicepay.com", {"{alicePayParameter: 1}"}},
- {"https://bobpay.com", {"{bobPayParameter: 2}"}}},
- /*per_method_quota=*/true));
- EXPECT_TRUE(
+ {"https://bobpay.com", {"{bobPayParameter: 2}"}}}));
+ EXPECT_FALSE(
guard_.CanQuery(GURL("https://example.com"), GURL("https://example.com"),
{{"https://bobpay.com", {"{bobPayParameter: 2}"}},
- {"basic-card", {"{supportedNetworks: ['visa']}"}}},
- /*per_method_quota=*/true));
+ {"basic-card", {"{supportedNetworks: ['visa']}"}}}));
}
// A website cannot query several different payment methods without the
@@ -166,18 +146,15 @@ TEST_F(CanMakePaymentQueryTest,
EXPECT_TRUE(
guard_.CanQuery(GURL("https://example.com"), GURL("https://example.com"),
{{"basic-card", {"{supportedNetworks: ['visa']}"}},
- {"https://alicepay.com", {"{alicePayParameter: 1}"}}},
- /*per_method_quota=*/false));
+ {"https://alicepay.com", {"{alicePayParameter: 1}"}}}));
EXPECT_FALSE(
guard_.CanQuery(GURL("https://example.com"), GURL("https://example.com"),
{{"https://alicepay.com", {"{alicePayParameter: 1}"}},
- {"https://bobpay.com", {"{bobPayParameter: 2}"}}},
- /*per_method_quota=*/false));
+ {"https://bobpay.com", {"{bobPayParameter: 2}"}}}));
EXPECT_FALSE(
guard_.CanQuery(GURL("https://example.com"), GURL("https://example.com"),
{{"https://bobpay.com", {"{bobPayParameter: 2}"}},
- {"basic-card", {"{supportedNetworks: ['visa']}"}}},
- /*per_method_quota=*/false));
+ {"basic-card", {"{supportedNetworks: ['visa']}"}}}));
}
// An instance of a website with per-method quota enabled (e.g., through an
@@ -192,69 +169,57 @@ TEST_F(CanMakePaymentQueryTest, SameWebsiteDifferentQuotaPolicy) {
EXPECT_TRUE(
guard_.CanQuery(GURL("https://example.com"), GURL("https://example.com"),
{{"basic-card", {"{supportedNetworks: ['visa']}"}},
- {"https://alicepay.com", {"{alicePayParameter: 1}"}}},
- /*per_method_quota=*/true));
- EXPECT_TRUE(
+ {"https://alicepay.com", {"{alicePayParameter: 1}"}}}));
+ EXPECT_FALSE(
guard_.CanQuery(GURL("https://example.com"), GURL("https://example.com"),
{{"https://alicepay.com", {"{alicePayParameter: 1}"}},
- {"https://bobpay.com", {"{bobPayParameter: 2}"}}},
- /*per_method_quota=*/true));
- EXPECT_TRUE(
+ {"https://bobpay.com", {"{bobPayParameter: 2}"}}}));
+ EXPECT_FALSE(
guard_.CanQuery(GURL("https://example.com"), GURL("https://example.com"),
{{"https://bobpay.com", {"{bobPayParameter: 2}"}},
- {"basic-card", {"{supportedNetworks: ['visa']}"}}},
- /*per_method_quota=*/true));
+ {"basic-card", {"{supportedNetworks: ['visa']}"}}}));
// Second instance of https://example.com has per-method quota feature
// disabled and so can only repeat the first query.
EXPECT_FALSE(
guard_.CanQuery(GURL("https://example.com"), GURL("https://example.com"),
{{"https://alicepay.com", {"{alicePayParameter: 1}"}},
- {"https://bobpay.com", {"{bobPayParameter: 2}"}}},
- /*per_method_quota=*/false));
+ {"https://bobpay.com", {"{bobPayParameter: 2}"}}}));
EXPECT_TRUE(
guard_.CanQuery(GURL("https://example.com"), GURL("https://example.com"),
{{"basic-card", {"{supportedNetworks: ['visa']}"}},
- {"https://alicepay.com", {"{alicePayParameter: 1}"}}},
- /*per_method_quota=*/false));
+ {"https://alicepay.com", {"{alicePayParameter: 1}"}}}));
EXPECT_FALSE(
guard_.CanQuery(GURL("https://example.com"), GURL("https://example.com"),
{{"https://bobpay.com", {"{bobPayParameter: 2}"}},
- {"basic-card", {"{supportedNetworks: ['visa']}"}}},
- /*per_method_quota=*/false));
+ {"basic-card", {"{supportedNetworks: ['visa']}"}}}));
// The two website queries can be interleaved any number of times in any order
// with the same results.
EXPECT_TRUE(
guard_.CanQuery(GURL("https://example.com"), GURL("https://example.com"),
{{"basic-card", {"{supportedNetworks: ['visa']}"}},
- {"https://alicepay.com", {"{alicePayParameter: 1}"}}},
- /*per_method_quota=*/true));
- EXPECT_TRUE(
+ {"https://alicepay.com", {"{alicePayParameter: 1}"}}}));
+ EXPECT_FALSE(
guard_.CanQuery(GURL("https://example.com"), GURL("https://example.com"),
{{"https://bobpay.com", {"{bobPayParameter: 2}"}},
- {"basic-card", {"{supportedNetworks: ['visa']}"}}},
- /*per_method_quota=*/true));
+ {"basic-card", {"{supportedNetworks: ['visa']}"}}}));
EXPECT_FALSE(
guard_.CanQuery(GURL("https://example.com"), GURL("https://example.com"),
{{"https://alicepay.com", {"{alicePayParameter: 1}"}},
- {"https://bobpay.com", {"{bobPayParameter: 2}"}}},
- /*per_method_quota=*/false));
+ {"https://bobpay.com", {"{bobPayParameter: 2}"}}}));
EXPECT_TRUE(
guard_.CanQuery(GURL("https://example.com"), GURL("https://example.com"),
{{"basic-card", {"{supportedNetworks: ['visa']}"}},
- {"https://alicepay.com", {"{alicePayParameter: 1}"}}},
- /*per_method_quota=*/false));
- EXPECT_TRUE(
+ {"https://alicepay.com", {"{alicePayParameter: 1}"}}}));
+ EXPECT_FALSE(
guard_.CanQuery(GURL("https://example.com"), GURL("https://example.com"),
{{"https://alicepay.com", {"{alicePayParameter: 1}"}},
- {"https://bobpay.com", {"{bobPayParameter: 2}"}}},
- /*per_method_quota=*/true));
+ {"https://bobpay.com", {"{bobPayParameter: 2}"}}}));
EXPECT_FALSE(
guard_.CanQuery(GURL("https://example.com"), GURL("https://example.com"),
{{"https://bobpay.com", {"{bobPayParameter: 2}"}},
- {"basic-card", {"{supportedNetworks: ['visa']}"}}},
- /*per_method_quota=*/false));
+ {"basic-card", {"{supportedNetworks: ['visa']}"}}}));
}
} // namespace
diff --git a/chromium/components/payments/core/chrome_os_error_strings.cc b/chromium/components/payments/core/chrome_os_error_strings.cc
new file mode 100644
index 00000000000..27bdd8c3370
--- /dev/null
+++ b/chromium/components/payments/core/chrome_os_error_strings.cc
@@ -0,0 +1,22 @@
+// Copyright 2020 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/chrome_os_error_strings.h"
+
+namespace payments {
+namespace errors {
+
+const char kEmptyResponse[] = "Android app response is empty.";
+
+const char kInvalidResponse[] = "Android app response is not valid.";
+
+const char kMoreThanOneActivity[] =
+ "Found more than one PAY activity in the Trusted Web Activity, but at most "
+ "one activity is supported.";
+
+const char kMoreThanOneMethodData[] =
+ "At most one payment method specific data is supported.";
+
+} // namespace errors
+} // namespace payments
diff --git a/chromium/components/payments/core/chrome_os_error_strings.h b/chromium/components/payments/core/chrome_os_error_strings.h
new file mode 100644
index 00000000000..13c4be4b35e
--- /dev/null
+++ b/chromium/components/payments/core/chrome_os_error_strings.h
@@ -0,0 +1,30 @@
+// Copyright 2020 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_CHROME_OS_ERROR_STRINGS_H_
+#define COMPONENTS_PAYMENTS_CORE_CHROME_OS_ERROR_STRINGS_H_
+
+namespace payments {
+namespace errors {
+
+// Developer facing error messages that are used only on Chrome OS.
+
+// Used if ARC sends a null object to the browser.
+extern const char kEmptyResponse[];
+
+// Used if ARC sends an object ot the browser that has neither an error message
+// nor a valid response.
+extern const char kInvalidResponse[];
+
+// Used when the TWA declares more than one PAY activity.
+extern const char kMoreThanOneActivity[];
+
+// Used when the merchant invokes the Trusted Web Activity with more than set of
+// payment method specific data.
+extern const char kMoreThanOneMethodData[];
+
+} // namespace errors
+} // namespace payments
+
+#endif // COMPONENTS_PAYMENTS_CORE_CHROME_OS_ERROR_STRINGS_H_
diff --git a/chromium/components/payments/core/error_message_util.cc b/chromium/components/payments/core/error_message_util.cc
index 1fdf75f77c2..dd96cbba18f 100644
--- a/chromium/components/payments/core/error_message_util.cc
+++ b/chromium/components/payments/core/error_message_util.cc
@@ -9,27 +9,46 @@
#include "base/check.h"
#include "base/strings/string_util.h"
+#include "components/payments/core/error_strings.h"
#include "components/payments/core/native_error_strings.h"
namespace payments {
-std::string GetNotSupportedErrorMessage(const std::set<std::string>& methods) {
- if (methods.empty())
- return errors::kGenericPaymentMethodNotSupportedMessage;
+namespace {
- std::vector<std::string> with_quotes(methods.size());
+template <class Collection>
+std::string concatNamesWithQuotesAndCommma(const Collection& names) {
+ std::vector<std::string> with_quotes(names.size());
std::transform(
- methods.begin(), methods.end(), with_quotes.begin(),
+ names.begin(), names.end(), with_quotes.begin(),
[](const std::string& method_name) { return "\"" + method_name + "\""; });
+ std::string result = base::JoinString(with_quotes, ", ");
+ return result;
+}
+
+} // namespace
+
+std::string GetNotSupportedErrorMessage(const std::set<std::string>& methods) {
+ if (methods.empty())
+ return errors::kGenericPaymentMethodNotSupportedMessage;
std::string output;
bool replaced = base::ReplaceChars(
- with_quotes.size() == 1
- ? errors::kSinglePaymentMethodNotSupportedFormat
- : errors::kMultiplePaymentMethodsNotSupportedFormat,
- "$", base::JoinString(with_quotes, ", "), &output);
+ methods.size() == 1 ? errors::kSinglePaymentMethodNotSupportedFormat
+ : errors::kMultiplePaymentMethodsNotSupportedFormat,
+ "$", concatNamesWithQuotesAndCommma(methods), &output);
DCHECK(replaced);
return output;
}
+std::string GetAppsSkippedForPartialDelegationErrorMessage(
+ const std::vector<std::string>& skipped_app_names) {
+ std::string output;
+ bool replaced = base::ReplaceChars(
+ errors::kSkipAppForPartialDelegation, "$",
+ concatNamesWithQuotesAndCommma(skipped_app_names), &output);
+
+ DCHECK(replaced);
+ return output;
+}
} // namespace payments
diff --git a/chromium/components/payments/core/error_message_util.h b/chromium/components/payments/core/error_message_util.h
index a99870480a9..5a09197d370 100644
--- a/chromium/components/payments/core/error_message_util.h
+++ b/chromium/components/payments/core/error_message_util.h
@@ -7,6 +7,7 @@
#include <set>
#include <string>
+#include <vector>
namespace payments {
@@ -14,6 +15,11 @@ namespace payments {
// not supported.
std::string GetNotSupportedErrorMessage(const std::set<std::string>& methods);
+// Returns a developer-facing error message that the apps are skipped because
+// they do not support full delegation.
+std::string GetAppsSkippedForPartialDelegationErrorMessage(
+ const std::vector<std::string>& skipped_apps);
+
} // namespace payments
#endif // COMPONENTS_PAYMENTS_CORE_ERROR_MESSAGE_UTIL_H_
diff --git a/chromium/components/payments/core/error_strings.cc b/chromium/components/payments/core/error_strings.cc
index 992734553e9..87c83302a6f 100644
--- a/chromium/components/payments/core/error_strings.cc
+++ b/chromium/components/payments/core/error_strings.cc
@@ -24,6 +24,7 @@ const char kMethodNameRequired[] = "Method name required.";
const char kMissingDetailsFromPaymentApp[] = "Payment app returned invalid response. Missing field \"details\".";
const char kMissingMethodNameFromPaymentApp[] = "Payment app returned invalid response. Missing field \"methodName\".";
const char kNotInASecureOrigin[] = "Not in a secure origin.";
+const char kNoWebContents[] = "The frame that initiated payment is not associated with any web page.";
const char kPayerEmailEmpty[] = "Payment app returned invalid response. Missing field \"payerEmail\".";
const char kPayerNameEmpty[] = "Payment app returned invalid response. Missing field \"payerName\".";
const char kPayerPhoneEmpty[] = "Payment app returned invalid response. Missing field \"payerPhone\".";
@@ -32,6 +33,7 @@ const char kProhibitedOriginOrInvalidSslExplanation[] = "No UI will be shown. Ca
const char kShippingAddressInvalid[] = "Payment app returned invalid shipping address in response.";
const char kShippingOptionEmpty[] = "Payment app returned invalid response. Missing field \"shipping option\".";
const char kShippingOptionIdRequired[] = "Shipping option identifier required.";
+const char kSkipAppForPartialDelegation[] = "Skipping $ for not providing all of the requested PaymentOptions.";
const char kStrictBasicCardShowReject[] = "User does not have valid information on file.";
const char kTotalRequired[] = "Total required.";
const char kUserCancelled[] = "User closed the Payment Request UI.";
diff --git a/chromium/components/payments/core/error_strings.h b/chromium/components/payments/core/error_strings.h
index bfd208d0c11..063a401132e 100644
--- a/chromium/components/payments/core/error_strings.h
+++ b/chromium/components/payments/core/error_strings.h
@@ -58,6 +58,9 @@ extern const char kMissingMethodNameFromPaymentApp[];
// The PaymentRequest API is available only on secure origins.
extern const char kNotInASecureOrigin[];
+// WebContents is not available from RenderFrameHost.
+extern const char kNoWebContents[];
+
// The payment handler responded with an empty "payer name" field.
extern const char kPayerNameEmpty[];
@@ -83,6 +86,10 @@ extern const char kShippingOptionEmpty[];
// Used when non-empty "shippingOptionId": "" is required, but not provided.
extern const char kShippingOptionIdRequired[];
+// Used when an app is skipped for supporting only part of the requested payment
+// options.
+extern const char kSkipAppForPartialDelegation[];
+
// Used when rejecting show() with NotSupportedError, because the user did not
// have all valid autofill data.
extern const char kStrictBasicCardShowReject[];
diff --git a/chromium/components/payments/core/features.cc b/chromium/components/payments/core/features.cc
index bc4b1a5e6de..89f088bc722 100644
--- a/chromium/components/payments/core/features.cc
+++ b/chromium/components/payments/core/features.cc
@@ -4,6 +4,8 @@
#include "components/payments/core/features.h"
+#include "build/build_config.h"
+
namespace payments {
namespace features {
@@ -37,13 +39,18 @@ const base::Feature kWebPaymentsJustInTimePaymentApp{
const base::Feature kAlwaysAllowJustInTimePaymentApp{
"AlwaysAllowJustInTimePaymentApp", base::FEATURE_DISABLED_BY_DEFAULT};
-const base::Feature kWebPaymentsPerMethodCanMakePaymentQuota{
- "WebPaymentsPerMethodCanMakePaymentQuota",
- base::FEATURE_DISABLED_BY_DEFAULT};
-
const base::Feature kWebPaymentsRedactShippingAddress{
"WebPaymentsRedactShippingAddress", base::FEATURE_ENABLED_BY_DEFAULT};
+const base::Feature kAppStoreBilling {
+ "AppStoreBilling",
+#if defined(OS_ANDROID)
+ base::FEATURE_ENABLED_BY_DEFAULT
+#else
+ base::FEATURE_DISABLED_BY_DEFAULT
+#endif // OS_ANDROID
+};
+
const base::Feature kAppStoreBillingDebug{"AppStoreBillingDebug",
base::FEATURE_DISABLED_BY_DEFAULT};
@@ -66,5 +73,17 @@ const base::Feature kAllowJITInstallationWhenAppIconIsMissing{
"AllowJITInstallationWhenAppIconIsMissing",
base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kEnforceFullDelegation{"EnforceFullDelegation",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+
+const base::Feature kSecurePaymentConfirmation {
+ "SecurePaymentConfirmation",
+#if defined(OS_MAC)
+ base::FEATURE_ENABLED_BY_DEFAULT
+#else
+ base::FEATURE_DISABLED_BY_DEFAULT
+#endif // OS_MAC
+};
+
} // namespace features
} // namespace payments
diff --git a/chromium/components/payments/core/features.h b/chromium/components/payments/core/features.h
index 273a692ca27..3b8c7547928 100644
--- a/chromium/components/payments/core/features.h
+++ b/chromium/components/payments/core/features.h
@@ -35,6 +35,10 @@ extern const base::Feature kWebPaymentsModifiers;
// with a single URL based payment app and no other info requested.
extern const base::Feature kWebPaymentsSingleAppUiSkip;
+// Used to control whether the invoking TWA can handle payments for app store
+// payment method identifiers.
+extern const base::Feature kAppStoreBilling;
+
// Used to control whether to remove the restriction that TWA has to be
// installed from specific app stores.
extern const base::Feature kAppStoreBillingDebug;
@@ -46,9 +50,6 @@ extern const base::Feature kWebPaymentsJustInTimePaymentApp;
// basic-card is also requested.
extern const base::Feature kAlwaysAllowJustInTimePaymentApp;
-// Used to control whether canMakePayment() quota is per-method.
-extern const base::Feature kWebPaymentsPerMethodCanMakePaymentQuota;
-
// Used to control whether the shipping address returned for the
// ShippingAddressChangeEvent is redacted of fine-grained details.
extern const base::Feature kWebPaymentsRedactShippingAddress;
@@ -75,6 +76,15 @@ extern const base::Feature kPaymentHandlerPopUpSizeWindow;
// Used to test icon refetch for JIT installed apps with missing icons.
extern const base::Feature kAllowJITInstallationWhenAppIconIsMissing;
+// Used to reject the apps with partial delegation.
+extern const base::Feature kEnforceFullDelegation;
+
+// Browser-side feature flag for SecurePaymentConfirmation, which can be used to
+// disable the feature. The feature is also controlled by the Blink runtime
+// feature "SecurePaymentConfirmation". Both have to be enabled for
+// SecurePaymentConfirmation to be available.
+extern const base::Feature kSecurePaymentConfirmation;
+
} // namespace features
} // namespace payments
diff --git a/chromium/components/payments/core/journey_logger.cc b/chromium/components/payments/core/journey_logger.cc
index 92e7b958cff..06769aa57e0 100644
--- a/chromium/components/payments/core/journey_logger.cc
+++ b/chromium/components/payments/core/journey_logger.cc
@@ -179,6 +179,7 @@ void JourneyLogger::SetRequestedInformation(bool requested_shipping,
void JourneyLogger::SetRequestedPaymentMethodTypes(
bool requested_basic_card,
bool requested_method_google,
+ bool requested_method_secure_payment_confirmation,
bool requested_method_other) {
if (requested_basic_card)
SetEventOccurred(EVENT_REQUEST_METHOD_BASIC_CARD);
@@ -186,6 +187,9 @@ void JourneyLogger::SetRequestedPaymentMethodTypes(
if (requested_method_google)
SetEventOccurred(EVENT_REQUEST_METHOD_GOOGLE);
+ if (requested_method_secure_payment_confirmation)
+ SetEventOccurred(EVENT_REQUEST_METHOD_SECURE_PAYMENT_CONFIRMATION);
+
if (requested_method_other)
SetEventOccurred(EVENT_REQUEST_METHOD_OTHER);
}
@@ -264,6 +268,10 @@ void JourneyLogger::RecordTransactionAmount(std::string currency,
.Record(ukm::UkmRecorder::Get());
}
+void JourneyLogger::RecordCheckoutStep(CheckoutFunnelStep step) {
+ base::UmaHistogramEnumeration("PaymentRequest.CheckoutFunnel", step);
+}
+
void JourneyLogger::RecordJourneyStatsHistograms(
CompletionStatus completion_status) {
if (has_recorded_) {
@@ -275,6 +283,23 @@ void JourneyLogger::RecordJourneyStatsHistograms(
RecordEventsMetric(completion_status);
RecordTimeToCheckout(completion_status);
+ // Depending on the completion status record kPaymentRequestTriggered and/or
+ // kCompleted checkout steps.
+ switch (completion_status) {
+ case COMPLETION_STATUS_COMPLETED:
+ RecordCheckoutStep(CheckoutFunnelStep::kPaymentRequestTriggered);
+ RecordCheckoutStep(CheckoutFunnelStep::kCompleted);
+ break;
+ case COMPLETION_STATUS_USER_ABORTED:
+ case COMPLETION_STATUS_OTHER_ABORTED:
+ RecordCheckoutStep(CheckoutFunnelStep::kPaymentRequestTriggered);
+ break;
+ case COMPLETION_STATUS_COULD_NOT_SHOW:
+ break;
+ default:
+ NOTREACHED();
+ }
+
// These following metrics only make sense if the Payment Request was
// triggered.
if (WasPaymentRequestTriggered()) {
@@ -409,6 +434,8 @@ void JourneyLogger::RecordTimeToCheckout(
selected_method_suffix = ".BasicCard";
} else if (events_ & EVENT_SELECTED_GOOGLE) {
selected_method_suffix = ".Google";
+ } else if (events_ & EVENT_SELECTED_SECURE_PAYMENT_CONFIRMATION) {
+ selected_method_suffix = ".SecurePaymentConfirmation";
} else {
DCHECK(events_ & EVENT_SELECTED_OTHER);
selected_method_suffix = ".Other";
@@ -460,6 +487,7 @@ void JourneyLogger::ValidateEventBits() const {
bit_vector.push_back(events_ & EVENT_SELECTED_CREDIT_CARD);
bit_vector.push_back(events_ & EVENT_SELECTED_GOOGLE);
bit_vector.push_back(events_ & EVENT_SELECTED_OTHER);
+ bit_vector.push_back(events_ & EVENT_SELECTED_SECURE_PAYMENT_CONFIRMATION);
DCHECK(ValidateExclusiveBitVector(bit_vector));
bit_vector.clear();
}
@@ -469,6 +497,8 @@ void JourneyLogger::ValidateEventBits() const {
DCHECK(events_ & EVENT_REQUEST_METHOD_BASIC_CARD);
} else if (events_ & EVENT_SELECTED_GOOGLE) {
DCHECK(events_ & EVENT_REQUEST_METHOD_GOOGLE);
+ } else if (events_ & EVENT_SELECTED_SECURE_PAYMENT_CONFIRMATION) {
+ DCHECK(events_ & EVENT_REQUEST_METHOD_SECURE_PAYMENT_CONFIRMATION);
} else if (events_ & EVENT_SELECTED_OTHER) {
// It is possible that a service worker based app responds to "basic-card"
// request.
@@ -488,6 +518,9 @@ 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));
+ // Internal secure payment confirmation payment handler should not skip UI
+ // show.
+ DCHECK(!(events_ & EVENT_SELECTED_SECURE_PAYMENT_CONFIRMATION));
}
// Check that the two bits are not set at the same time.
diff --git a/chromium/components/payments/core/journey_logger.h b/chromium/components/payments/core/journey_logger.h
index 4a3d7df3e12..fdf7c1c83af 100644
--- a/chromium/components/payments/core/journey_logger.h
+++ b/chromium/components/payments/core/journey_logger.h
@@ -62,7 +62,7 @@ class JourneyLogger {
EVENT_SKIPPED_SHOW = 1 << 3,
// .complete() was called by the merchant, completing the flow.
EVENT_COMPLETED = 1 << 4,
- // The user aborted the flow by either dismissing it explicitely, or
+ // The user aborted the flow by either dismissing it explicitly, or
// navigating away (if possible).
EVENT_USER_ABORTED = 1 << 5,
// Other reasons for aborting include the merchant calling .abort(), the
@@ -108,7 +108,12 @@ class JourneyLogger {
EVENT_AVAILABLE_METHOD_BASIC_CARD = 1 << 27,
EVENT_AVAILABLE_METHOD_GOOGLE = 1 << 28,
EVENT_AVAILABLE_METHOD_OTHER = 1 << 29,
- EVENT_ENUM_MAX = 1 << 30,
+
+ // Bits for secure-payment-confirmation method.
+ EVENT_REQUEST_METHOD_SECURE_PAYMENT_CONFIRMATION = 1 << 30,
+ EVENT_SELECTED_SECURE_PAYMENT_CONFIRMATION = 1 << 31,
+
+ EVENT_ENUM_MAX = EVENT_SELECTED_SECURE_PAYMENT_CONFIRMATION,
};
// The reason why the Payment Request was aborted.
@@ -152,6 +157,30 @@ class JourneyLogger {
kMaxValue = kRegularTransaction,
};
+ // Records different checkout steps for payment requests. The difference
+ // between number of requests recorded for each step and its successor shows
+ // the drop-off that happened during that step.
+ // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.components.payments
+ // GENERATED_JAVA_CLASS_NAME_OVERRIDE: CheckoutFunnelStep
+ enum class CheckoutFunnelStep {
+ // Payment request has been initiated.
+ kInitiated = 0,
+ // .show() has been called. (a.k.a. the user has clicked on the checkout/buy
+ // button on merchant's site.)
+ kShowCalled = 1,
+ // Payment request UI has been shown or skipped in favor of the payment
+ // handler UI. Drop-off before this step means that the browser could not
+ // proceed with the payment request. (e.g. because of no payment app being
+ // available for requested payment method(s) or an unsecured origin.)
+ kPaymentRequestTriggered = 2,
+ // Payment handler UI has been invoked either by skipping to it directly or
+ // the user clicking on the "Continue" button in payment sheet UI.
+ kPaymentHandlerInvoked = 3,
+ // Payment request has been completed with 'success' status.
+ kCompleted = 4,
+ kMaxValue = kCompleted,
+ };
+
JourneyLogger(bool is_incognito, ukm::SourceId payment_request_source_id);
~JourneyLogger();
@@ -187,12 +216,15 @@ class JourneyLogger {
bool requested_name);
// Records the requested payment method types. A value should be true if at
- // least one payment method in the category (basic-card, google payment method
- // or other url-based payment method, respectively) is requested.
+ // least one payment method in the category (basic-card, google payment
+ // method, secure payment confirmation method or other url-based payment
+ // method, respectively) is requested.
// TODO(crbug.com/754811): Add support for non-basic-card, non-URL methods.
- void SetRequestedPaymentMethodTypes(bool requested_basic_card,
- bool requested_method_google,
- bool requested_method_other);
+ void SetRequestedPaymentMethodTypes(
+ bool requested_basic_card,
+ bool requested_method_google,
+ bool requested_method_secure_payment_confirmation,
+ bool requested_method_other);
// Records that the Payment Request was completed successfully, and starts the
// logging of all the journey metrics.
@@ -206,16 +238,19 @@ class JourneyLogger {
// reason.
void SetNotShown(NotShownReason reason);
- // Records the transcation amount after converting to USD separated by
+ // Records the transaction amount after converting to USD separated by
// completion status (complete vs triggered).
void RecordTransactionAmount(std::string currency,
const std::string& value,
bool completed);
+ // Increments the bucket count for the given checkout step.
+ void RecordCheckoutStep(CheckoutFunnelStep step);
+
// Records when Payment Request .show is called.
void SetTriggerTime();
- // Sets the ukm source id of the selected app when it gets invoked.
+ // Sets the UKM source id of the selected app when it gets invoked.
void SetPaymentAppUkmSourceId(ukm::SourceId payment_app_source_id);
private:
diff --git a/chromium/components/payments/core/journey_logger_unittest.cc b/chromium/components/payments/core/journey_logger_unittest.cc
index c430e07138c..468e4e71750 100644
--- a/chromium/components/payments/core/journey_logger_unittest.cc
+++ b/chromium/components/payments/core/journey_logger_unittest.cc
@@ -97,6 +97,7 @@ TEST(JourneyLoggerTest,
logger.SetRequestedInformation(true, false, false, false);
logger.SetRequestedPaymentMethodTypes(
/*requested_basic_card=*/true, /*requested_method_google=*/false,
+ /*requested_method_secure_payment_confirmation=*/false,
/*requested_method_other=*/false);
logger.SetEventOccurred(JourneyLogger::EVENT_PAY_CLICKED);
logger.SetEventOccurred(JourneyLogger::EVENT_SELECTED_CREDIT_CARD);
@@ -214,6 +215,7 @@ TEST(JourneyLoggerTest,
logger.SetRequestedInformation(true, false, false, false);
logger.SetRequestedPaymentMethodTypes(
/*requested_basic_card=*/true, /*requested_method_google=*/false,
+ /*requested_method_secure_payment_confirmation=*/false,
/*requested_method_other=*/false);
logger.SetCanMakePaymentValue(false);
logger.SetEventOccurred(JourneyLogger::EVENT_PAY_CLICKED);
@@ -291,6 +293,7 @@ TEST(JourneyLoggerTest,
logger.SetRequestedInformation(true, false, false, false);
logger.SetRequestedPaymentMethodTypes(
/*requested_basic_card=*/true, /*requested_method_google=*/false,
+ /*requested_method_secure_payment_confirmation=*/false,
/*requested_method_other=*/false);
logger.SetCanMakePaymentValue(true);
logger.SetEventOccurred(JourneyLogger::EVENT_PAY_CLICKED);
@@ -320,6 +323,7 @@ TEST(JourneyLoggerTest,
logger.SetRequestedInformation(true, false, false, false);
logger.SetRequestedPaymentMethodTypes(
/*requested_basic_card=*/true, /*requested_method_google=*/false,
+ /*requested_method_secure_payment_confirmation=*/false,
/*requested_method_other=*/false);
logger.SetCanMakePaymentValue(true);
logger.SetEventOccurred(JourneyLogger::EVENT_PAY_CLICKED);
@@ -349,6 +353,7 @@ TEST(JourneyLoggerTest,
/*requested_phone=*/false, /*requested_name=*/false);
logger.SetRequestedPaymentMethodTypes(
/*requested_basic_card=*/true, /*requested_method_google=*/true,
+ /*requested_method_secure_payment_confirmation=*/false,
/*requested_method_other=*/false);
// Simulate that the user had suggestions for all the requested sections.
@@ -396,6 +401,7 @@ TEST(JourneyLoggerTest,
/*requested_phone=*/false, /*requested_name=*/false);
logger.SetRequestedPaymentMethodTypes(
/*requested_basic_card=*/true, /*requested_method_google=*/true,
+ /*requested_method_secure_payment_confirmation=*/false,
/*requested_method_other=*/false);
// Simulate that the user had suggestions for all the requested sections.
@@ -441,6 +447,7 @@ TEST(JourneyLoggerTest,
/*requested_phone=*/false, /*requested_name=*/false);
logger.SetRequestedPaymentMethodTypes(
/*requested_basic_card=*/true, /*requested_method_google=*/true,
+ /*requested_method_secure_payment_confirmation=*/false,
/*requested_method_other=*/false);
// Simulate that the user had suggestions for all the requested sections.
@@ -487,6 +494,7 @@ TEST(JourneyLoggerTest,
/*requested_phone=*/false, /*requested_name=*/false);
logger.SetRequestedPaymentMethodTypes(
/*requested_basic_card=*/true, /*requested_method_google=*/true,
+ /*requested_method_secure_payment_confirmation=*/false,
/*requested_method_other=*/false);
// Simulate that the user had suggestions for all the requested sections.
@@ -534,6 +542,7 @@ TEST(JourneyLoggerTest,
/*requested_phone=*/false, /*requested_name=*/false);
logger.SetRequestedPaymentMethodTypes(
/*requested_basic_card=*/true, /*requested_method_google=*/true,
+ /*requested_method_secure_payment_confirmation=*/false,
/*requested_method_other=*/false);
// Simulate that the user had suggestions for none of the requested sections.
@@ -581,6 +590,7 @@ TEST(JourneyLoggerTest,
/*requested_phone=*/false, /*requested_name=*/false);
logger.SetRequestedPaymentMethodTypes(
/*requested_basic_card=*/true, /*requested_method_google=*/true,
+ /*requested_method_secure_payment_confirmation=*/false,
/*requested_method_other=*/false);
// Simulate that the user had suggestions for none of the requested sections.
@@ -626,6 +636,7 @@ TEST(JourneyLoggerTest,
/*requested_phone=*/false, /*requested_name=*/false);
logger.SetRequestedPaymentMethodTypes(
/*requested_basic_card=*/true, /*requested_method_google=*/true,
+ /*requested_method_secure_payment_confirmation=*/false,
/*requested_method_other=*/false);
// Simulate that the user had suggestions for none of the requested sections.
@@ -672,6 +683,7 @@ TEST(JourneyLoggerTest,
/*requested_phone=*/false, /*requested_name=*/false);
logger.SetRequestedPaymentMethodTypes(
/*requested_basic_card=*/true, /*requested_method_google=*/true,
+ /*requested_method_secure_payment_confirmation=*/false,
/*requested_method_other=*/false);
// Simulate that the user had suggestions for none of the requested sections.
@@ -718,6 +730,7 @@ TEST(
/*requested_phone=*/false, /*requested_name=*/false);
logger.SetRequestedPaymentMethodTypes(
/*requested_basic_card=*/true, /*requested_method_google=*/true,
+ /*requested_method_secure_payment_confirmation=*/false,
/*requested_method_other=*/false);
// Simulate that the user had incomplete suggestions for the requested
@@ -765,6 +778,7 @@ TEST(
/*requested_phone=*/false, /*requested_name=*/false);
logger.SetRequestedPaymentMethodTypes(
/*requested_basic_card=*/true, /*requested_method_google=*/true,
+ /*requested_method_secure_payment_confirmation=*/false,
/*requested_method_other=*/false);
// Simulate that the user had incomplete suggestions for one of the requested
@@ -814,6 +828,7 @@ TEST(
/*requested_phone=*/false, /*requested_name=*/false);
logger.SetRequestedPaymentMethodTypes(
/*requested_basic_card=*/true, /*requested_method_google=*/true,
+ /*requested_method_secure_payment_confirmation=*/false,
/*requested_method_other=*/false);
// Simulate that the user had incomplete suggestions for one of the requested
@@ -863,6 +878,7 @@ TEST(JourneyLoggerTest, RecordJourneyStatsHistograms_TwoPaymentRequests) {
/*requested_phone=*/false, /*requested_name=*/false);
logger1.SetRequestedPaymentMethodTypes(
/*requested_basic_card=*/true, /*requested_method_google=*/true,
+ /*requested_method_secure_payment_confirmation=*/false,
/*requested_method_other=*/true);
logger2.SetEventOccurred(JourneyLogger::EVENT_SHOWN);
logger2.SetRequestedInformation(
@@ -870,6 +886,7 @@ TEST(JourneyLoggerTest, RecordJourneyStatsHistograms_TwoPaymentRequests) {
/*requested_phone=*/false, /*requested_name=*/false);
logger2.SetRequestedPaymentMethodTypes(
/*requested_basic_card=*/false, /*requested_method_google=*/false,
+ /*requested_method_secure_payment_confirmation=*/false,
/*requested_method_other=*/true);
logger1.SetCanMakePaymentValue(true);
@@ -945,6 +962,7 @@ TEST(JourneyLoggerTest,
/*requested_phone=*/false, /*requested_name=*/false);
logger.SetRequestedPaymentMethodTypes(
/*requested_basic_card=*/true, /*requested_method_google=*/false,
+ /*requested_method_secure_payment_confirmation=*/false,
/*requested_method_other=*/false);
// Simulate that the user aborts after being shown the Payment Request and
@@ -996,6 +1014,7 @@ TEST(JourneyLoggerTest,
/*requested_phone=*/false, /*requested_name=*/false);
logger.SetRequestedPaymentMethodTypes(
/*requested_basic_card=*/true, /*requested_method_google=*/false,
+ /*requested_method_secure_payment_confirmation=*/false,
/*requested_method_other=*/false);
// Simulate that the user aborts after being shown the Payment Request.
diff --git a/chromium/components/payments/core/method_strings.cc b/chromium/components/payments/core/method_strings.cc
index 177d810616c..bcd0248dbcd 100644
--- a/chromium/components/payments/core/method_strings.cc
+++ b/chromium/components/payments/core/method_strings.cc
@@ -18,6 +18,7 @@ const char kGooglePlayBilling[] = "https://play.google.com/billing";
const char kInterledger[] = "interledger";
const char kPayeeCreditTransfer[] = "payee-credit-transfer";
const char kPayerCreditTransfer[] = "payer-credit-transfer";
+const char kSecurePaymentConfirmation[] = "secure-payment-confirmation";
const char kTokenizedCard[] = "tokenized-card";
} // namespace methods
diff --git a/chromium/components/payments/core/method_strings.h b/chromium/components/payments/core/method_strings.h
index 25f97e8b6cb..90a199f5bc5 100644
--- a/chromium/components/payments/core/method_strings.h
+++ b/chromium/components/payments/core/method_strings.h
@@ -40,6 +40,10 @@ extern const char kPayeeCreditTransfer[];
// https://w3c.github.io/payment-method-credit-transfer/
extern const char kPayerCreditTransfer[];
+// Secure Payment Confirmation method name.
+// https://github.com/rsolomakhin/secure-payment-confirmation/
+extern const char kSecurePaymentConfirmation[];
+
// Tokenized Card method name.
// https://w3c.github.io/webpayments-methods-tokenization/
extern const char kTokenizedCard[];
diff --git a/chromium/components/payments/core/native_error_strings.cc b/chromium/components/payments/core/native_error_strings.cc
index 1d2a70c8e33..477f11da3da 100644
--- a/chromium/components/payments/core/native_error_strings.cc
+++ b/chromium/components/payments/core/native_error_strings.cc
@@ -201,5 +201,22 @@ const char kNoContentAndNoLinkHeader[] =
const char kNoContentInPaymentManifest[] =
"No content found in payment manifest \"$1\".";
+const char kUnableToInvokeAndroidPaymentApps[] =
+ "Unable to invoke Android apps.";
+
+const char kUserClosedPaymentApp[] = "User closed the payment app.";
+
+const char kMoreThanOneService[] =
+ "Found more than one IS_READY_TO_PAY service, but at most one service is "
+ "supported.";
+
+const char kCredentialIdsRequired[] =
+ "The \"secure-payment-confirmation\" method requires a non-empty "
+ "\"credentialIds\" array of non-empty arrays.";
+
+const char kTimeoutTooLong[] =
+ "The \"secure-payment-confirmation\" method requires at most 1 hour "
+ "\"timeout\" field.";
+
} // namespace errors
} // namespace payments
diff --git a/chromium/components/payments/core/native_error_strings.h b/chromium/components/payments/core/native_error_strings.h
index d70468ab0e3..e1f6a0465a2 100644
--- a/chromium/components/payments/core/native_error_strings.h
+++ b/chromium/components/payments/core/native_error_strings.h
@@ -222,10 +222,29 @@ extern const char kGenericPaymentMethodNotSupportedMessage[];
// be used with base::ReplaceStringPlaceholders(fmt, {A}, nullptr).
extern const char kNoContentAndNoLinkHeader[];
-// User when the downloaded payment manifest A is empty. This format should be
+// Used when the downloaded payment manifest A is empty. This format should be
// used with base::ReplaceStringPlaceholders(fmt, {A}, nullptr).
extern const char kNoContentInPaymentManifest[];
+// Used when it's impossible to invoke Android payment apps, e.g., when ARC is
+// disabled on Chrome OS.
+extern const char kUnableToInvokeAndroidPaymentApps[];
+
+// Used when the user has closed the payment app. For example, An Android app
+// indicates this by returning Activity.RESULT_CANCELED.
+extern const char kUserClosedPaymentApp[];
+
+// Used when an Android app declares more than one IS_READY_TO_PAY service.
+extern const char kMoreThanOneService[];
+
+// Used when no credential IDs are specified for the
+// "secure-payment-confirmation" method.
+extern const char kCredentialIdsRequired[];
+
+// Used when the timeout specified for the "secure-payment-confirmation" method
+// is too long.
+extern const char kTimeoutTooLong[];
+
} // namespace errors
} // namespace payments
diff --git a/chromium/components/payments/core/payment_manifest_downloader_unittest.cc b/chromium/components/payments/core/payment_manifest_downloader_unittest.cc
index 9d72ec2bd5d..4cf6aabde2a 100644
--- a/chromium/components/payments/core/payment_manifest_downloader_unittest.cc
+++ b/chromium/components/payments/core/payment_manifest_downloader_unittest.cc
@@ -11,7 +11,6 @@
#include "base/threading/thread_task_runner_handle.h"
#include "components/payments/core/error_logger.h"
#include "net/http/http_response_headers.h"
-#include "net/url_request/url_request_test_util.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"
diff --git a/chromium/components/payments/core/payment_request_data_util.cc b/chromium/components/payments/core/payment_request_data_util.cc
index 9d7766dc959..b3f87858338 100644
--- a/chromium/components/payments/core/payment_request_data_util.cc
+++ b/chromium/components/payments/core/payment_request_data_util.cc
@@ -4,7 +4,7 @@
#include "components/payments/core/payment_request_data_util.h"
-#include <memory>
+#include <utility>
#include "base/stl_util.h"
#include "base/strings/string_split.h"
@@ -166,5 +166,19 @@ base::string16 FormatCardNumberForDisplay(const base::string16& card_number) {
return number;
}
+std::unique_ptr<std::map<std::string, std::set<std::string>>>
+FilterStringifiedMethodData(
+ const std::map<std::string, std::set<std::string>>& stringified_method_data,
+ const std::set<std::string>& supported_payment_method_names) {
+ auto result =
+ std::make_unique<std::map<std::string, std::set<std::string>>>();
+ for (const auto& pair : stringified_method_data) {
+ if (base::Contains(supported_payment_method_names, pair.first)) {
+ result->insert({pair.first, pair.second});
+ }
+ }
+ return result;
+}
+
} // 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 633d5173260..bdaaaae2719 100644
--- a/chromium/components/payments/core/payment_request_data_util.h
+++ b/chromium/components/payments/core/payment_request_data_util.h
@@ -5,6 +5,7 @@
#ifndef COMPONENTS_PAYMENTS_CORE_PAYMENT_REQUEST_DATA_UTIL_H_
#define COMPONENTS_PAYMENTS_CORE_PAYMENT_REQUEST_DATA_UTIL_H_
+#include <memory>
#include <set>
#include <string>
#include <vector>
@@ -64,6 +65,24 @@ void ParseSupportedMethods(
// numbers, which start with a letter.
base::string16 FormatCardNumberForDisplay(const base::string16& card_number);
+// Returns the subset of |stringified_method_data| map where the keys are in the
+// |supported_payment_method_names| set. Used for ensuring that a payment app
+// will not be queried about payment method names that it does not support.
+//
+// FilterStringifiedMethodData({"a": {"b"}: "c": {"d"}}, {"a"}) -> {"a": {"b"}}
+//
+// Both the return value and the first parameter to the function have the
+// following format:
+// Key: Payment method identifier, such as "example-test" or
+// "https://example.test".
+// Value: The set of all payment method specific parameters for the given
+// payment method identifier, each one serialized into a JSON string,
+// e.g., '{"key": "value"}'.
+std::unique_ptr<std::map<std::string, std::set<std::string>>>
+FilterStringifiedMethodData(
+ const std::map<std::string, std::set<std::string>>& stringified_method_data,
+ const std::set<std::string>& supported_payment_method_names);
+
} // 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 b034fca5f37..19f2578b3a5 100644
--- a/chromium/components/payments/core/payment_request_data_util_unittest.cc
+++ b/chromium/components/payments/core/payment_request_data_util_unittest.cc
@@ -4,7 +4,10 @@
#include "components/payments/core/payment_request_data_util.h"
+#include <map>
#include <memory>
+#include <set>
+#include <string>
#include "base/json/json_writer.h"
#include "base/macros.h"
@@ -274,5 +277,25 @@ TEST(PaymentRequestDataUtil, ParseSupportedMethods_MultipleEntries) {
UnorderedElementsAre(kBasicCardMethodName, kBobPayMethod));
}
+TEST(PaymentRequestDataUtil, FilterStringifiedMethodData) {
+ std::map<std::string, std::set<std::string>> requested;
+ std::set<std::string> supported;
+ EXPECT_TRUE(FilterStringifiedMethodData(requested, supported)->empty());
+
+ requested["a"].insert("{\"b\": \"c\"}");
+ EXPECT_TRUE(FilterStringifiedMethodData(requested, supported)->empty());
+
+ requested["x"].insert("{\"y\": \"z\"}");
+ EXPECT_TRUE(FilterStringifiedMethodData(requested, supported)->empty());
+
+ supported.insert("x");
+ std::map<std::string, std::set<std::string>> expected;
+ expected["x"].insert("{\"y\": \"z\"}");
+ EXPECT_EQ(expected, *FilterStringifiedMethodData(requested, supported));
+
+ supported.insert("g");
+ EXPECT_EQ(expected, *FilterStringifiedMethodData(requested, supported));
+}
+
} // 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 dadb21e5018..e0356aa10bd 100644
--- a/chromium/components/payments/core/payment_request_delegate.h
+++ b/chromium/components/payments/core/payment_request_delegate.h
@@ -14,9 +14,16 @@ class PaymentRequest;
class PaymentRequestDelegate : public PaymentRequestBaseDelegate {
public:
+ enum class DialogType {
+ PAYMENT_REQUEST,
+ SECURE_PAYMENT_CONFIRMATION,
+ };
+
PaymentRequestDelegate();
~PaymentRequestDelegate() override;
+ void set_dialog_type(DialogType dialog_type) { dialog_type_ = dialog_type; }
+
// Shows the Payment Request dialog for the given |request|.
virtual void ShowDialog(PaymentRequest* request) = 0;
@@ -39,6 +46,9 @@ class PaymentRequestDelegate : public PaymentRequestBaseDelegate {
// Returns a weak pointer to this delegate.
base::WeakPtr<PaymentRequestDelegate> GetWeakPtr();
+ protected:
+ DialogType dialog_type_ = DialogType::PAYMENT_REQUEST;
+
private:
base::WeakPtrFactory<PaymentRequestDelegate> weak_ptr_factory_{this};
};
diff --git a/chromium/components/payments/core/secure_payment_confirmation_instrument.cc b/chromium/components/payments/core/secure_payment_confirmation_instrument.cc
new file mode 100644
index 00000000000..1674561bc05
--- /dev/null
+++ b/chromium/components/payments/core/secure_payment_confirmation_instrument.cc
@@ -0,0 +1,32 @@
+// Copyright 2020 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/secure_payment_confirmation_instrument.h"
+
+#include <utility>
+
+namespace payments {
+
+SecurePaymentConfirmationInstrument::SecurePaymentConfirmationInstrument() =
+ default;
+
+SecurePaymentConfirmationInstrument::SecurePaymentConfirmationInstrument(
+ std::vector<uint8_t> credential_id,
+ const std::string& relying_party_id,
+ const base::string16& label,
+ std::vector<uint8_t> icon)
+ : credential_id(std::move(credential_id)),
+ relying_party_id(relying_party_id),
+ label(label),
+ icon(std::move(icon)) {}
+
+SecurePaymentConfirmationInstrument::~SecurePaymentConfirmationInstrument() =
+ default;
+
+bool SecurePaymentConfirmationInstrument::IsValid() const {
+ return !credential_id.empty() && !relying_party_id.empty() &&
+ !label.empty() && !icon.empty();
+}
+
+} // namespace payments \ No newline at end of file
diff --git a/chromium/components/payments/core/secure_payment_confirmation_instrument.h b/chromium/components/payments/core/secure_payment_confirmation_instrument.h
new file mode 100644
index 00000000000..8540b760a8e
--- /dev/null
+++ b/chromium/components/payments/core/secure_payment_confirmation_instrument.h
@@ -0,0 +1,51 @@
+// Copyright 2020 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_SECURE_PAYMENT_CONFIRMATION_INSTRUMENT_H_
+#define COMPONENTS_PAYMENTS_CORE_SECURE_PAYMENT_CONFIRMATION_INSTRUMENT_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/strings/string16.h"
+
+namespace payments {
+
+// Secure payment information instrument information that can be stored in
+// SQLite database.
+struct SecurePaymentConfirmationInstrument {
+ // Constructs an empty instrument. This instrument is not valid until all
+ // fields are populated.
+ SecurePaymentConfirmationInstrument();
+
+ // Constructs an instrument with the given fields. Please use `std::move()`
+ // when passing the `credential_id` and the `icon` byte arrays to avoid
+ // excessive copying.
+ SecurePaymentConfirmationInstrument(std::vector<uint8_t> credential_id,
+ const std::string& relying_party_id,
+ const base::string16& label,
+ std::vector<uint8_t> icon);
+
+ ~SecurePaymentConfirmationInstrument();
+
+ // An instrument should not be copied or assigned.
+ SecurePaymentConfirmationInstrument(
+ const SecurePaymentConfirmationInstrument& other) = delete;
+ SecurePaymentConfirmationInstrument& operator=(
+ const SecurePaymentConfirmationInstrument& other) = delete;
+
+ // Checks instrument validity.
+ bool IsValid() const;
+
+ std::vector<uint8_t> credential_id;
+ std::string relying_party_id;
+ base::string16 label;
+ std::vector<uint8_t> icon;
+};
+
+} // namespace payments
+
+#endif // COMPONENTS_PAYMENTS_CORE_SECURE_PAYMENT_CONFIRMATION_INSTRUMENT_H_ \ No newline at end of file
diff --git a/chromium/components/payments/core/test_payment_request_delegate.h b/chromium/components/payments/core/test_payment_request_delegate.h
index df0b9dca01b..ff638f5f3aa 100644
--- a/chromium/components/payments/core/test_payment_request_delegate.h
+++ b/chromium/components/payments/core/test_payment_request_delegate.h
@@ -14,7 +14,6 @@
#include "components/autofill/core/browser/test_address_normalizer.h"
#include "components/autofill/core/browser/test_autofill_client.h"
#include "components/payments/core/payment_request_delegate.h"
-#include "net/url_request/url_request_test_util.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"