summaryrefslogtreecommitdiff
path: root/chromium/components/payments/content
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/components/payments/content')
-rw-r--r--chromium/components/payments/content/BUILD.gn7
-rw-r--r--chromium/components/payments/content/DEPS3
-rw-r--r--chromium/components/payments/content/android/BUILD.gn45
-rw-r--r--chromium/components/payments/content/android/DEPS1
-rw-r--r--chromium/components/payments/content/android/byte_buffer_helper.h2
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/Address.java19
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/IPaymentDetailsUpdateService.aidl44
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/IPaymentDetailsUpdateServiceCallback.aidl29
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/JniPaymentApp.java260
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/JourneyLogger.java253
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/MojoStructCollection.java30
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/OWNERS3
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/PackageManagerDelegate.java140
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentApp.java67
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentDetailsUpdateService.java76
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentDetailsUpdateServiceHelper.java245
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentFeatureList.java81
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentHandlerHost.java85
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestSpec.java79
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestUpdateEventListener.java68
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/IsReadyToPayServiceHelper.java14
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelper.java131
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelperType.java251
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/payment_details_update_service.aidl6
-rw-r--r--chromium/components/payments/content/android/java_templates/ErrorStrings.java.tmpl3
-rw-r--r--chromium/components/payments/content/android/jni_payment_app.cc254
-rw-r--r--chromium/components/payments/content/android/jni_payment_app.h101
-rw-r--r--chromium/components/payments/content/android/payment_feature_list.cc69
-rw-r--r--chromium/components/payments/content/android/payment_feature_list.h21
-rw-r--r--chromium/components/payments/content/android/payment_handler_host.cc68
-rw-r--r--chromium/components/payments/content/android/payment_handler_host.h64
-rw-r--r--chromium/components/payments/content/android/payment_request_spec.cc90
-rw-r--r--chromium/components/payments/content/android/payment_request_spec.h66
-rw-r--r--chromium/components/payments/content/android/payment_request_update_event_listener.cc50
-rw-r--r--chromium/components/payments/content/android/payment_request_update_event_listener.h37
-rw-r--r--chromium/components/payments/content/autofill_payment_app.cc244
-rw-r--r--chromium/components/payments/content/autofill_payment_app.h120
-rw-r--r--chromium/components/payments/content/autofill_payment_app_factory.cc2
-rw-r--r--chromium/components/payments/content/autofill_payment_app_unittest.cc362
-rw-r--r--chromium/components/payments/content/content_payment_request_delegate.h11
-rw-r--r--chromium/components/payments/content/installable_payment_app_crawler.cc100
-rw-r--r--chromium/components/payments/content/installable_payment_app_crawler.h24
-rw-r--r--chromium/components/payments/content/manifest_verifier.cc3
-rw-r--r--chromium/components/payments/content/payment_app.cc182
-rw-r--r--chromium/components/payments/content/payment_app.h204
-rw-r--r--chromium/components/payments/content/payment_app_factory.h26
-rw-r--r--chromium/components/payments/content/payment_app_service.cc6
-rw-r--r--chromium/components/payments/content/payment_app_service.h3
-rw-r--r--chromium/components/payments/content/payment_app_unittest.cc24
-rw-r--r--chromium/components/payments/content/payment_handler_host.cc8
-rw-r--r--chromium/components/payments/content/payment_handler_host.h3
-rw-r--r--chromium/components/payments/content/payment_request.cc67
-rw-r--r--chromium/components/payments/content/payment_request.h18
-rw-r--r--chromium/components/payments/content/payment_request_spec.cc2
-rw-r--r--chromium/components/payments/content/payment_request_state.cc30
-rw-r--r--chromium/components/payments/content/payment_request_state.h33
-rw-r--r--chromium/components/payments/content/payment_request_state_unittest.cc5
-rw-r--r--chromium/components/payments/content/payment_response_helper.h2
-rw-r--r--chromium/components/payments/content/payment_response_helper_unittest.cc2
-rw-r--r--chromium/components/payments/content/service_worker_payment_app.cc152
-rw-r--r--chromium/components/payments/content/service_worker_payment_app.h69
-rw-r--r--chromium/components/payments/content/service_worker_payment_app_factory.cc49
-rw-r--r--chromium/components/payments/content/service_worker_payment_app_finder.cc172
-rw-r--r--chromium/components/payments/content/service_worker_payment_app_finder.h27
-rw-r--r--chromium/components/payments/content/service_worker_payment_app_unittest.cc26
-rw-r--r--chromium/components/payments/content/test_content_payment_request_delegate.cc8
-rw-r--r--chromium/components/payments/content/test_content_payment_request_delegate.h3
67 files changed, 4086 insertions, 663 deletions
diff --git a/chromium/components/payments/content/BUILD.gn b/chromium/components/payments/content/BUILD.gn
index cd3fc208122..ab9091d996b 100644
--- a/chromium/components/payments/content/BUILD.gn
+++ b/chromium/components/payments/content/BUILD.gn
@@ -6,12 +6,16 @@ import("//build/config/jumbo.gni")
jumbo_static_library("content") {
sources = [
+ "autofill_payment_app.cc",
+ "autofill_payment_app.h",
"autofill_payment_app_factory.cc",
"autofill_payment_app_factory.h",
"can_make_payment_query_factory.cc",
"can_make_payment_query_factory.h",
"initialization_task.cc",
"initialization_task.h",
+ "payment_app.cc",
+ "payment_app.h",
"payment_app_factory.cc",
"payment_app_factory.h",
"payment_app_service.cc",
@@ -137,6 +141,7 @@ jumbo_source_set("unit_tests") {
if (!is_android) {
sources += [
+ "autofill_payment_app_unittest.cc",
"payment_app_unittest.cc",
"payment_request_spec_unittest.cc",
"payment_request_state_unittest.cc",
@@ -158,7 +163,9 @@ jumbo_source_set("unit_tests") {
"//components/strings:components_strings_grit",
"//components/webdata/common",
"//content/test:test_support",
+ "//net:test_support",
"//services/metrics/public/cpp:metrics_cpp",
+ "//services/network:test_support",
"//sql",
"//testing/gtest",
"//third_party/blink/public:blink_headers",
diff --git a/chromium/components/payments/content/DEPS b/chromium/components/payments/content/DEPS
index 58b31c99fee..117c4ac539b 100644
--- a/chromium/components/payments/content/DEPS
+++ b/chromium/components/payments/content/DEPS
@@ -13,6 +13,9 @@ include_rules = [
"+net",
"+services/data_decoder/public/cpp",
"+services/metrics/public/cpp",
+ "+services/network/public/cpp",
+ "+services/network/public/mojom",
+ "+services/network/test",
"+sql",
"+third_party/blink/public/common",
"+third_party/blink/public/mojom/devtools/console_message.mojom.h",
diff --git a/chromium/components/payments/content/android/BUILD.gn b/chromium/components/payments/content/android/BUILD.gn
index 72c630f27ec..ba4855549cb 100644
--- a/chromium/components/payments/content/android/BUILD.gn
+++ b/chromium/components/payments/content/android/BUILD.gn
@@ -13,13 +13,21 @@ static_library("android") {
"currency_formatter_android.cc",
"currency_formatter_android.h",
"error_message_util.cc",
+ "jni_payment_app.cc",
+ "jni_payment_app.h",
"origin_security_checker_android.cc",
+ "payment_feature_list.cc",
+ "payment_feature_list.h",
"payment_handler_host.cc",
"payment_handler_host.h",
"payment_manifest_downloader_android.cc",
"payment_manifest_downloader_android.h",
"payment_manifest_parser_android.cc",
"payment_manifest_parser_android.h",
+ "payment_request_spec.cc",
+ "payment_request_spec.h",
+ "payment_request_update_event_listener.cc",
+ "payment_request_update_event_listener.h",
"payment_validator_android.cc",
"url_util.cc",
]
@@ -42,10 +50,15 @@ generate_jni("jni_headers") {
sources = [
"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/OriginSecurityChecker.java",
+ "java/src/org/chromium/components/payments/PaymentFeatureList.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/PaymentRequestSpec.java",
+ "java/src/org/chromium/components/payments/PaymentRequestUpdateEventListener.java",
"java/src/org/chromium/components/payments/PaymentValidator.java",
"java/src/org/chromium/components/payments/UrlUtil.java",
]
@@ -57,14 +70,23 @@ android_library("java") {
"java/src/org/chromium/components/payments/Address.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/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/PaymentDetailsConverter.java",
+ "java/src/org/chromium/components/payments/PaymentDetailsUpdateService.java",
+ "java/src/org/chromium/components/payments/PaymentDetailsUpdateServiceHelper.java",
+ "java/src/org/chromium/components/payments/PaymentFeatureList.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/PaymentRequestSpec.java",
+ "java/src/org/chromium/components/payments/PaymentRequestUpdateEventListener.java",
"java/src/org/chromium/components/payments/PaymentValidator.java",
"java/src/org/chromium/components/payments/UrlUtil.java",
"java/src/org/chromium/components/payments/WebAppManifestSection.java",
@@ -83,7 +105,20 @@ android_library("java") {
"//url:gurl_java",
"//url:origin_java",
]
- srcjar_deps = [ ":error_strings_generated_srcjar" ]
+ srcjar_deps = [
+ ":error_strings_generated_srcjar",
+ ":payment_app_type_generated_enum",
+ ":payment_details_update_service_aidl",
+ ":payments_journey_logger_enum_javagen",
+ ]
+}
+
+android_aidl("payment_details_update_service_aidl") {
+ interface_file = "java/src/org/chromium/components/payments/payment_details_update_service.aidl"
+ sources = [
+ "java/src/org/chromium/components/payments/IPaymentDetailsUpdateService.aidl",
+ "java/src/org/chromium/components/payments/IPaymentDetailsUpdateServiceCallback.aidl",
+ ]
}
java_cpp_strings("error_strings_generated_srcjar") {
@@ -97,3 +132,11 @@ java_cpp_strings("method_strings_generated_srcjar") {
template = "java_templates/MethodStrings.java.tmpl"
}
+
+java_cpp_enum("payment_app_type_generated_enum") {
+ sources = [ "//components/payments/content/payment_app.h" ]
+}
+
+java_cpp_enum("payments_journey_logger_enum_javagen") {
+ sources = [ "//components/payments/core/journey_logger.h" ]
+}
diff --git a/chromium/components/payments/content/android/DEPS b/chromium/components/payments/content/android/DEPS
index c81c188ed20..9edb460c289 100644
--- a/chromium/components/payments/content/android/DEPS
+++ b/chromium/components/payments/content/android/DEPS
@@ -1,4 +1,5 @@
include_rules = [
"+components/payments/content/android/jni_headers",
+ "+mojo/public/java",
"+services/network/public/cpp",
]
diff --git a/chromium/components/payments/content/android/byte_buffer_helper.h b/chromium/components/payments/content/android/byte_buffer_helper.h
index fd21d4a2b93..bf1b5d36c4b 100644
--- a/chromium/components/payments/content/android/byte_buffer_helper.h
+++ b/chromium/components/payments/content/android/byte_buffer_helper.h
@@ -10,7 +10,7 @@
#include <vector>
#include "base/android/scoped_java_ref.h"
-#include "base/logging.h"
+#include "base/check.h"
#include "mojo/public/cpp/bindings/struct_ptr.h"
namespace payments {
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/Address.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/Address.java
index 0d106e4bffe..ee7a4418a3a 100644
--- a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/Address.java
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/Address.java
@@ -8,11 +8,21 @@ import android.os.Bundle;
import androidx.annotation.Nullable;
+import java.util.regex.Pattern;
+
/**
* An immutable class that mirrors org.chromium.payments.mojom.PaymentAddress.
* https://w3c.github.io/payment-request/#paymentaddress-interface
*/
public class Address {
+ /**
+ * The pattern for a valid country code:
+ * https://w3c.github.io/payment-request/#internal-constructor
+ */
+ private static final String COUNTRY_CODE_PATTERN = "^[A-Z]{2}$";
+ @Nullable
+ private static Pattern sCountryCodePattern;
+
public final String country;
public final String[] addressLine;
public final String region;
@@ -69,7 +79,7 @@ public class Address {
}
// Keys in shipping address bundle.
- public static final String EXTRA_ADDRESS_COUNTRY = "country";
+ public static final String EXTRA_ADDRESS_COUNTRY = "countryCode";
public static final String EXTRA_ADDRESS_LINES = "addressLines";
public static final String EXTRA_ADDRESS_REGION = "region";
public static final String EXTRA_ADDRESS_CITY = "city";
@@ -102,4 +112,11 @@ public class Address {
private static String getStringOrEmpty(Bundle bundle, String key) {
return bundle.getString(key, /*defaultValue =*/"");
}
+
+ public boolean isValid() {
+ if (sCountryCodePattern == null) {
+ sCountryCodePattern = Pattern.compile(COUNTRY_CODE_PATTERN);
+ }
+ return sCountryCodePattern.matcher(country).matches();
+ }
}
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/IPaymentDetailsUpdateService.aidl b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/IPaymentDetailsUpdateService.aidl
new file mode 100644
index 00000000000..f5db4b4538a
--- /dev/null
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/IPaymentDetailsUpdateService.aidl
@@ -0,0 +1,44 @@
+// 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 android.os.Bundle;
+
+import IPaymentDetailsUpdateServiceCallback;
+
+/**
+ * Helper interface used by the invoked native payment app to notify the
+ * browser that the user has selected a different payment method, shipping
+ * option, or shipping address.
+ */
+interface IPaymentDetailsUpdateService {
+ /**
+ * Called to notify the browser that the user has selected a different
+ * payment method.
+ *
+ * @param paymentHandlerMethodData The data containing the selected payment
+ * method's name and optional stringified details.
+ */
+ oneway void changePaymentMethod(in Bundle paymentHandlerMethodData,
+ IPaymentDetailsUpdateServiceCallback callback);
+
+ /**
+ * Called to notify the browser that the user has selected a different
+ * shipping option.
+ *
+ * @param shippingOptionId The identifier of the selected shipping option.
+ */
+ oneway void changeShippingOption(in String shippingOptionId,
+ IPaymentDetailsUpdateServiceCallback callback);
+
+ /**
+ * Called to notify the browser that the user has selected a different
+ * shipping address.
+ *
+ * @param shippingAddress The selected shipping address.
+ */
+ oneway void changeShippingAddress(in Bundle shippingAddress,
+ IPaymentDetailsUpdateServiceCallback callback);
+}
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/IPaymentDetailsUpdateServiceCallback.aidl b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/IPaymentDetailsUpdateServiceCallback.aidl
new file mode 100644
index 00000000000..a79898ab20c
--- /dev/null
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/IPaymentDetailsUpdateServiceCallback.aidl
@@ -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.
+
+package org.chromium.components.payments;
+
+import android.os.Bundle;
+
+/**
+ * Helper interface used by the browser to notify the invoked native app about
+ * merchant's response to one of the paymentmethodchange, shippingoptionchange,
+ * or shippingaddresschange events.
+ */
+interface IPaymentDetailsUpdateServiceCallback {
+ /**
+ * Called to notify the invoked payment app about updated payment details
+ * received from the merchant.
+ *
+ * @param updatedPaymentDetails The updated payment details received from
+ * the merchant.
+ */
+ oneway void updateWith(in Bundle updatedPaymentDetails);
+
+ /**
+ * Called to notify the invoked payment app that the merchant has not
+ * modified the payment details.
+ */
+ oneway void paymentDetailsNotUpdated();
+}
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/JniPaymentApp.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/JniPaymentApp.java
new file mode 100644
index 00000000000..c214d6b1d97
--- /dev/null
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/JniPaymentApp.java
@@ -0,0 +1,260 @@
+// 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 android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.Handler;
+
+import androidx.annotation.Nullable;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.NativeMethods;
+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 java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/** Wrapper around a C++ payment app. */
+@JNINamespace("payments")
+public class JniPaymentApp extends PaymentApp {
+ private final Handler mHandler = new Handler();
+ private final @PaymentAppType int mPaymentAppType;
+
+ // The Java object owns the C++ payment app and frees it in dismissInstrument().
+ private long mNativeObject;
+
+ private AbortCallback mAbortCallback;
+ private InstrumentDetailsCallback mInvokeCallback;
+
+ @CalledByNative
+ private JniPaymentApp(String id, String label, String sublabel, Bitmap icon,
+ @PaymentAppType int paymentAppType, long nativeObject) {
+ super(id, label, sublabel, new BitmapDrawable(icon));
+ mPaymentAppType = paymentAppType;
+ mNativeObject = nativeObject;
+ }
+
+ @CalledByNative
+ public void onAbortResult(boolean aborted) {
+ mHandler.post(() -> {
+ if (mAbortCallback == null) return;
+ mAbortCallback.onInstrumentAbortResult(aborted);
+ mAbortCallback = null;
+ });
+ }
+
+ @CalledByNative
+ public void onInvokeResult(String methodName, String stringifiedDetails, PayerData payerData) {
+ mHandler.post(() -> {
+ if (mInvokeCallback == null) return;
+ mInvokeCallback.onInstrumentDetailsReady(methodName, stringifiedDetails, payerData);
+ mInvokeCallback = null;
+ });
+ }
+
+ @CalledByNative
+ public void onInvokeError(String errorMessage) {
+ mHandler.post(() -> {
+ if (mInvokeCallback == null) return;
+ mInvokeCallback.onInstrumentDetailsError(errorMessage);
+ mInvokeCallback = null;
+ });
+ }
+
+ @CalledByNative
+ private static PayerData createPayerData(String payerName, String payerPhone, String payerEmail,
+ Address shippingAddress, String selectedShippingOptionId) {
+ return new PayerData(
+ payerName, payerPhone, payerEmail, shippingAddress, selectedShippingOptionId);
+ }
+
+ @CalledByNative
+ private static Address createShippingAddress(String country, String[] addressLine,
+ String region, String city, String dependentLocality, String postalCode,
+ String sortingCode, String organization, String recipient, String phone) {
+ return new Address(country, addressLine, region, city, dependentLocality, postalCode,
+ sortingCode, organization, recipient, phone);
+ }
+
+ @Override
+ public Set<String> getInstrumentMethodNames() {
+ return new HashSet<>(
+ Arrays.asList(JniPaymentAppJni.get().getInstrumentMethodNames(mNativeObject)));
+ }
+
+ @Override
+ public boolean isValidForPaymentMethodData(String method, @Nullable PaymentMethodData data) {
+ return JniPaymentAppJni.get().isValidForPaymentMethodData(
+ mNativeObject, method, data != null ? data.serialize() : null);
+ }
+
+ @Override
+ public boolean handlesShippingAddress() {
+ return JniPaymentAppJni.get().handlesShippingAddress(mNativeObject);
+ }
+
+ @Override
+ public boolean handlesPayerName() {
+ return JniPaymentAppJni.get().handlesPayerName(mNativeObject);
+ }
+
+ @Override
+ public boolean handlesPayerEmail() {
+ return JniPaymentAppJni.get().handlesPayerEmail(mNativeObject);
+ }
+
+ @Override
+ public boolean handlesPayerPhone() {
+ return JniPaymentAppJni.get().handlesPayerPhone(mNativeObject);
+ }
+
+ @Override
+ @Nullable
+ public String getCountryCode() {
+ return JniPaymentAppJni.get().getCountryCode(mNativeObject);
+ }
+
+ @Override
+ public boolean canMakePayment() {
+ return JniPaymentAppJni.get().canMakePayment(mNativeObject);
+ }
+
+ @Override
+ public boolean canPreselect() {
+ return JniPaymentAppJni.get().canPreselect(mNativeObject);
+ }
+
+ @Override
+ public boolean isUserGestureRequiredToSkipUi() {
+ return JniPaymentAppJni.get().isUserGestureRequiredToSkipUi(mNativeObject);
+ }
+
+ @Override
+ public void invokePaymentApp(String id, String merchantName, String origin, String iframeOrigin,
+ @Nullable byte[][] certificateChain, Map<String, PaymentMethodData> methodDataMap,
+ PaymentItem total, List<PaymentItem> displayItems,
+ Map<String, PaymentDetailsModifier> modifiers, PaymentOptions paymentOptions,
+ List<PaymentShippingOption> shippingOptions, InstrumentDetailsCallback callback) {
+ mInvokeCallback = callback;
+ JniPaymentAppJni.get().invokePaymentApp(mNativeObject, /*callback=*/this);
+ }
+
+ @Override
+ public void updateWith(PaymentRequestDetailsUpdate response) {
+ JniPaymentAppJni.get().updateWith(mNativeObject, response.serialize());
+ }
+
+ @Override
+ public void onPaymentDetailsNotUpdated() {
+ JniPaymentAppJni.get().onPaymentDetailsNotUpdated(mNativeObject);
+ }
+
+ @Override
+ public boolean isWaitingForPaymentDetailsUpdate() {
+ return JniPaymentAppJni.get().isWaitingForPaymentDetailsUpdate(mNativeObject);
+ }
+
+ @Override
+ public void abortPaymentApp(AbortCallback callback) {
+ mAbortCallback = callback;
+ JniPaymentAppJni.get().abortPaymentApp(mNativeObject, this);
+ }
+
+ @Override
+ public boolean isReadyForMinimalUI() {
+ return JniPaymentAppJni.get().isReadyForMinimalUI(mNativeObject);
+ }
+
+ @Override
+ @Nullable
+ public String accountBalance() {
+ return JniPaymentAppJni.get().accountBalance(mNativeObject);
+ }
+
+ @Override
+ public void disableShowingOwnUI() {
+ JniPaymentAppJni.get().disableShowingOwnUI(mNativeObject);
+ }
+
+ @Override
+ @Nullable
+ public String getApplicationIdentifierToHide() {
+ return JniPaymentAppJni.get().getApplicationIdentifierToHide(mNativeObject);
+ }
+
+ @Override
+ @Nullable
+ public Set<String> getApplicationIdentifiersThatHideThisApp() {
+ return new HashSet<>(Arrays.asList(
+ JniPaymentAppJni.get().getApplicationIdentifiersThatHideThisApp(mNativeObject)));
+ }
+
+ @Override
+ public long getUkmSourceId() {
+ return JniPaymentAppJni.get().getUkmSourceId(mNativeObject);
+ }
+
+ @Override
+ public void setPaymentHandlerHost(PaymentHandlerHost host) {
+ JniPaymentAppJni.get().setPaymentHandlerHost(mNativeObject, host);
+ }
+
+ @Override
+ public void dismissInstrument() {
+ if (mNativeObject == 0) return;
+ JniPaymentAppJni.get().freeNativeObject(mNativeObject);
+ mNativeObject = 0;
+ }
+
+ @Override
+ public void finalize() throws Throwable {
+ dismissInstrument();
+ super.finalize();
+ }
+
+ @Override
+ public @PaymentAppType int getPaymentAppType() {
+ return mPaymentAppType;
+ }
+
+ @NativeMethods
+ interface Natives {
+ String[] getInstrumentMethodNames(long nativeJniPaymentApp);
+ boolean isValidForPaymentMethodData(
+ long nativeJniPaymentApp, String method, ByteBuffer dataByteBuffer);
+ boolean handlesShippingAddress(long nativeJniPaymentApp);
+ boolean handlesPayerName(long nativeJniPaymentApp);
+ boolean handlesPayerEmail(long nativeJniPaymentApp);
+ boolean handlesPayerPhone(long nativeJniPaymentApp);
+ String getCountryCode(long nativeJniPaymentApp);
+ boolean canMakePayment(long nativeJniPaymentApp);
+ boolean canPreselect(long nativeJniPaymentApp);
+ boolean isUserGestureRequiredToSkipUi(long nativeJniPaymentApp);
+ void invokePaymentApp(long nativeJniPaymentApp, JniPaymentApp callback);
+ void updateWith(long nativeJniPaymentApp, ByteBuffer responseByteBuffer);
+ void onPaymentDetailsNotUpdated(long nativeJniPaymentApp);
+ boolean isWaitingForPaymentDetailsUpdate(long nativeJniPaymentApp);
+ void abortPaymentApp(long nativeJniPaymentApp, JniPaymentApp callback);
+ boolean isReadyForMinimalUI(long nativeJniPaymentApp);
+ String accountBalance(long nativeJniPaymentApp);
+ void disableShowingOwnUI(long nativeJniPaymentApp);
+ String getApplicationIdentifierToHide(long nativeJniPaymentApp);
+ String[] getApplicationIdentifiersThatHideThisApp(long nativeJniPaymentApp);
+ long getUkmSourceId(long nativeJniPaymentApp);
+ void setPaymentHandlerHost(long nativeJniPaymentApp, PaymentHandlerHost paymentHandlerHost);
+ void freeNativeObject(long nativeJniPaymentApp);
+ }
+}
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
new file mode 100644
index 00000000000..93a29b3b056
--- /dev/null
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/JourneyLogger.java
@@ -0,0 +1,253 @@
+// 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.
+
+package org.chromium.components.payments;
+
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.NativeMethods;
+import org.chromium.content_public.browser.WebContents;
+
+/**
+ * A class used to record journey metrics for the Payment Request feature.
+ */
+@JNINamespace("payments")
+public class JourneyLogger {
+ /**
+ * Pointer to the native implementation.
+ */
+ private long mJourneyLoggerAndroid;
+
+ private boolean mHasRecorded;
+
+ public JourneyLogger(boolean isIncognito, WebContents webContents) {
+ // 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(
+ JourneyLogger.this, isIncognito, webContents);
+ }
+
+ /** Will destroy the native object. This class shouldn't be used afterwards. */
+ public void destroy() {
+ if (mJourneyLoggerAndroid != 0) {
+ JourneyLoggerJni.get().destroy(mJourneyLoggerAndroid, JourneyLogger.this);
+ mJourneyLoggerAndroid = 0;
+ }
+ }
+
+ /**
+ * Sets the number of suggestions shown for the specified section.
+ *
+ * @param section The section for which to log.
+ * @param number The number of suggestions.
+ * @param hasCompleteSuggestion Whether the section has at least one
+ * complete suggestion.
+ */
+ public void setNumberOfSuggestionsShown(
+ int section, int number, boolean hasCompleteSuggestion) {
+ assert section < Section.MAX;
+ JourneyLoggerJni.get().setNumberOfSuggestionsShown(
+ mJourneyLoggerAndroid, JourneyLogger.this, section, number, hasCompleteSuggestion);
+ }
+
+ /**
+ * Increments the number of selection changes for the specified section.
+ *
+ * @param section The section for which to log.
+ */
+ public void incrementSelectionChanges(int section) {
+ assert section < Section.MAX;
+ JourneyLoggerJni.get().incrementSelectionChanges(
+ mJourneyLoggerAndroid, JourneyLogger.this, section);
+ }
+
+ /**
+ * Increments the number of selection edits for the specified section.
+ *
+ * @param section The section for which to log.
+ */
+ public void incrementSelectionEdits(int section) {
+ assert section < Section.MAX;
+ JourneyLoggerJni.get().incrementSelectionEdits(
+ mJourneyLoggerAndroid, JourneyLogger.this, section);
+ }
+
+ /**
+ * Increments the number of selection adds for the specified section.
+ *
+ * @param section The section for which to log.
+ */
+ public void incrementSelectionAdds(int section) {
+ assert section < Section.MAX;
+ JourneyLoggerJni.get().incrementSelectionAdds(
+ mJourneyLoggerAndroid, JourneyLogger.this, section);
+ }
+
+ /**
+ * Records the fact that the merchant called CanMakePayment and records its return value.
+ *
+ * @param value The return value of the CanMakePayment call.
+ */
+ public void setCanMakePaymentValue(boolean value) {
+ JourneyLoggerJni.get().setCanMakePaymentValue(
+ mJourneyLoggerAndroid, JourneyLogger.this, value);
+ }
+
+ /**
+ * Records the fact that the merchant called HasEnrolledInstrument and records its return value.
+ *
+ * @param value The return value of the HasEnrolledInstrument call.
+ */
+ public void setHasEnrolledInstrumentValue(boolean value) {
+ JourneyLoggerJni.get().setHasEnrolledInstrumentValue(
+ mJourneyLoggerAndroid, JourneyLogger.this, value);
+ }
+
+ /**
+ * Records that an event occurred.
+ *
+ * @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);
+ }
+
+ /*
+ * Records what user information were requested by the merchant to complete the Payment Request.
+ *
+ * @param requestShipping Whether the merchant requested a shipping address.
+ * @param requestEmail Whether the merchant requested an email address.
+ * @param requestPhone Whether the merchant requested a phone number.
+ * @param requestName Whether the merchant requestes a name.
+ */
+ public void setRequestedInformation(boolean requestShipping, boolean requestEmail,
+ boolean requestPhone, boolean requestName) {
+ JourneyLoggerJni.get().setRequestedInformation(mJourneyLoggerAndroid, JourneyLogger.this,
+ requestShipping, requestEmail, requestPhone, requestName);
+ }
+
+ /*
+ * Records what types of payment methods were requested by the merchant in the Payment Request.
+ *
+ * @param requestedBasicCard Whether the merchant requested basic-card.
+ * @param requestedMethodGoogle Whether the merchant requested a Google payment method.
+ * @param requestedMethodOther Whether the merchant requested a non basic-card, non-Google
+ * payment method.
+ */
+ public void setRequestedPaymentMethodTypes(boolean requestedBasicCard,
+ boolean requestedMethodGoogle, boolean requestedMethodOther) {
+ JourneyLoggerJni.get().setRequestedPaymentMethodTypes(mJourneyLoggerAndroid,
+ JourneyLogger.this, requestedBasicCard, requestedMethodGoogle,
+ requestedMethodOther);
+ }
+
+ /**
+ * Records that the Payment Request was completed sucessfully. Also starts the logging of
+ * all the journey logger metrics.
+ */
+ public void setCompleted() {
+ assert !mHasRecorded;
+
+ if (!mHasRecorded) {
+ mHasRecorded = true;
+ JourneyLoggerJni.get().setCompleted(mJourneyLoggerAndroid, JourneyLogger.this);
+ }
+ }
+
+ /**
+ * Records that the Payment Request was aborted and for what reason. Also starts the logging of
+ * all the journey logger metrics.
+ *
+ * @param reason An int indicating why the payment request was aborted.
+ */
+ public void setAborted(int reason) {
+ assert reason < AbortReason.MAX;
+
+ // The abort reasons on Android cascade into each other, so only the first one should be
+ // recorded.
+ if (!mHasRecorded) {
+ mHasRecorded = true;
+ JourneyLoggerJni.get().setAborted(mJourneyLoggerAndroid, JourneyLogger.this, reason);
+ }
+ }
+
+ /**
+ * Records that the Payment Request was not shown to the user and for what reason.
+ *
+ * @param reason An int indicating why the payment request was not shown.
+ */
+ public void setNotShown(int reason) {
+ assert reason < NotShownReason.MAX;
+ assert !mHasRecorded;
+
+ if (!mHasRecorded) {
+ mHasRecorded = true;
+ JourneyLoggerJni.get().setNotShown(mJourneyLoggerAndroid, JourneyLogger.this, reason);
+ }
+ }
+
+ /**
+ * Records amount of completed/triggered transactions separated by currency.
+ *
+ * @param curreny A string indicating the curreny of the transaction.
+ * @param value A string indicating the value of the transaction.
+ * @param completed A boolean indicating whether the transaction has completed or not.
+ */
+ public void recordTransactionAmount(String currency, String value, boolean completed) {
+ JourneyLoggerJni.get().recordTransactionAmount(
+ mJourneyLoggerAndroid, JourneyLogger.this, currency, value, completed);
+ }
+
+ /**
+ * Records the time when request.show() is called.
+ */
+ public void setTriggerTime() {
+ JourneyLoggerJni.get().setTriggerTime(mJourneyLoggerAndroid, JourneyLogger.this);
+ }
+
+ /**
+ * Sets the ukm source id of payment app.
+ * @param sourceId A long indicating the ukm source id of the invoked payment app.
+ */
+ public void setPaymentAppUkmSourceId(long sourceId) {
+ JourneyLoggerJni.get().setPaymentAppUkmSourceId(
+ mJourneyLoggerAndroid, JourneyLogger.this, sourceId);
+ }
+
+ @NativeMethods
+ interface Natives {
+ long initJourneyLoggerAndroid(
+ JourneyLogger caller, boolean isIncognito, WebContents webContents);
+ void destroy(long nativeJourneyLoggerAndroid, JourneyLogger caller);
+ void setNumberOfSuggestionsShown(long nativeJourneyLoggerAndroid, JourneyLogger caller,
+ int section, int number, boolean hasCompleteSuggestion);
+ void incrementSelectionChanges(
+ long nativeJourneyLoggerAndroid, JourneyLogger caller, int section);
+ void incrementSelectionEdits(
+ long nativeJourneyLoggerAndroid, JourneyLogger caller, int section);
+ void incrementSelectionAdds(
+ long nativeJourneyLoggerAndroid, JourneyLogger caller, int section);
+ void setCanMakePaymentValue(
+ long nativeJourneyLoggerAndroid, JourneyLogger caller, boolean value);
+ void setHasEnrolledInstrumentValue(
+ long nativeJourneyLoggerAndroid, JourneyLogger caller, boolean value);
+ void setEventOccurred(long nativeJourneyLoggerAndroid, JourneyLogger caller, int event);
+ void setRequestedInformation(long nativeJourneyLoggerAndroid, JourneyLogger caller,
+ boolean requestShipping, boolean requestEmail, boolean requestPhone,
+ boolean requestName);
+ void setRequestedPaymentMethodTypes(long nativeJourneyLoggerAndroid, JourneyLogger caller,
+ boolean requestedBasicCard, boolean requestedMethodGoogle,
+ boolean requestedMethodOther);
+ void setCompleted(long nativeJourneyLoggerAndroid, JourneyLogger caller);
+ void setAborted(long nativeJourneyLoggerAndroid, JourneyLogger caller, int reason);
+ void setNotShown(long nativeJourneyLoggerAndroid, JourneyLogger caller, int reason);
+ void recordTransactionAmount(long nativeJourneyLoggerAndroid, JourneyLogger caller,
+ String currency, String value, boolean completed);
+ 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/MojoStructCollection.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/MojoStructCollection.java
new file mode 100644
index 00000000000..1aabcb611a7
--- /dev/null
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/MojoStructCollection.java
@@ -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.
+
+package org.chromium.components.payments;
+
+import org.chromium.mojo.bindings.Struct;
+
+import java.nio.ByteBuffer;
+import java.util.Collection;
+
+/** Helper class for serializing a collection of Mojo structs. */
+public class MojoStructCollection {
+ /**
+ * Serialize a collection of Mojo structs.
+ * @param collection A collection of Mojo structs to serialize.
+ * @return An array of Mojo structs serialized into byte buffer objects.
+ */
+ public static <T extends Struct> ByteBuffer[] serialize(Collection<T> collection) {
+ ByteBuffer[] result = new ByteBuffer[collection.size()];
+ int i = 0;
+ for (T item : collection) {
+ result[i++] = item.serialize();
+ }
+ return result;
+ }
+
+ // Prevent instantiation.
+ private MojoStructCollection() {}
+}
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 971a6e658aa..db4625d4570 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
@@ -5,3 +5,6 @@ file://components/payments/OWNERS
per-file *TypeConverter*.*=set noparent
per-file *TypeConverter*.*=file://ipc/SECURITY_OWNERS
+
+per-file *.aidl=set noparent
+per-file *.aidl=file://ipc/SECURITY_OWNERS
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PackageManagerDelegate.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PackageManagerDelegate.java
new file mode 100644
index 00000000000..b84634c4da2
--- /dev/null
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PackageManagerDelegate.java
@@ -0,0 +1,140 @@
+// 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 android.annotation.SuppressLint;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.StrictMode;
+import android.os.StrictMode.ThreadPolicy;
+
+import androidx.annotation.Nullable;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.PackageManagerUtils;
+
+import java.util.List;
+
+/** Abstraction of Android's package manager to enable testing. */
+public class PackageManagerDelegate {
+ /**
+ * Checks whether the system has the given feature.
+ * @param feature The feature to check.
+ * @return Whether the system has the given feature.
+ */
+ public boolean hasSystemFeature(String feature) {
+ return ContextUtils.getApplicationContext().getPackageManager().hasSystemFeature(feature);
+ }
+
+ /**
+ * Retrieves package information of an installed application.
+ *
+ * @param packageName The package name of an installed application.
+ * @return The package information of the installed application.
+ */
+ @SuppressLint("PackageManagerGetSignatures")
+ public PackageInfo getPackageInfoWithSignatures(String packageName) {
+ try {
+ return ContextUtils.getApplicationContext().getPackageManager().getPackageInfo(
+ packageName, PackageManager.GET_SIGNATURES);
+ } catch (NameNotFoundException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Retrieves package information of an installed application.
+ *
+ * @param uid The uid of an installed application.
+ * @return The package information of the installed application.
+ */
+ @SuppressLint("PackageManagerGetSignatures")
+ public PackageInfo getPackageInfoWithSignatures(int uid) {
+ String packageName =
+ ContextUtils.getApplicationContext().getPackageManager().getNameForUid(uid);
+ if (packageName == null) return null;
+ return getPackageInfoWithSignatures(packageName);
+ }
+
+ /**
+ * Retrieves the list of activities that can respond to the given intent.
+ * @param intent The intent to query.
+ * @return The list of activities that can respond to the intent.
+ */
+ public List<ResolveInfo> getActivitiesThatCanRespondToIntent(Intent intent) {
+ return PackageManagerUtils.queryIntentActivities(intent, 0);
+ }
+
+ /**
+ * Retrieves the list of activities that can respond to the given intent. And returns the
+ * activites' meta data in ResolveInfo.
+ *
+ * @param intent The intent to query.
+ * @return The list of activities that can respond to the intent.
+ */
+ public List<ResolveInfo> getActivitiesThatCanRespondToIntentWithMetaData(Intent intent) {
+ return PackageManagerUtils.queryIntentActivities(intent, PackageManager.GET_META_DATA);
+ }
+
+ /**
+ * Retrieves the list of services that can respond to the given intent.
+ * @param intent The intent to query.
+ * @return The list of services that can respond to the intent.
+ */
+ public List<ResolveInfo> getServicesThatCanRespondToIntent(Intent intent) {
+ ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
+ try {
+ return ContextUtils.getApplicationContext().getPackageManager().queryIntentServices(
+ intent, 0);
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+ }
+
+ /**
+ * Retrieves the label of the app.
+ * @param resolveInfo The identifying information for an app.
+ * @return The label for this app.
+ */
+ public CharSequence getAppLabel(ResolveInfo resolveInfo) {
+ return resolveInfo.loadLabel(ContextUtils.getApplicationContext().getPackageManager());
+ }
+
+ /**
+ * Retrieves the icon of the app.
+ * @param resolveInfo The identifying information for an app.
+ * @return The icon for this app.
+ */
+ public Drawable getAppIcon(ResolveInfo resolveInfo) {
+ return resolveInfo.loadIcon(ContextUtils.getApplicationContext().getPackageManager());
+ }
+
+ /**
+ * Gets the string array resource of the given application.
+ *
+ * @param applicationInfo The application info.
+ * @param resourceId The identifier of the string array resource.
+ * @return The string array resource, or null if not found.
+ */
+ @Nullable
+ public String[] getStringArrayResourceForApplication(
+ ApplicationInfo applicationInfo, int resourceId) {
+ Resources resources;
+ try {
+ resources = ContextUtils.getApplicationContext()
+ .getPackageManager()
+ .getResourcesForApplication(applicationInfo);
+ } catch (NameNotFoundException e) {
+ return null;
+ }
+ return resources == null ? null : resources.getStringArray(resourceId);
+ }
+}
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 7f8d259b441..b719873aa8c 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
@@ -11,7 +11,6 @@ import androidx.annotation.Nullable;
import org.chromium.base.task.PostTask;
import org.chromium.components.autofill.EditableOption;
import org.chromium.content_public.browser.UiThreadTaskTraits;
-import org.chromium.payments.mojom.PaymentAddress;
import org.chromium.payments.mojom.PaymentDetailsModifier;
import org.chromium.payments.mojom.PaymentItem;
import org.chromium.payments.mojom.PaymentMethodData;
@@ -36,47 +35,6 @@ public abstract class PaymentApp extends EditableOption {
protected boolean mHaveRequestedAutofillData;
/**
- * The interface for listener to payment method, shipping address, and shipping option change
- * events. Note: What the spec calls "payment methods" in the context of a "change event", this
- * code calls "apps".
- */
- public interface PaymentRequestUpdateEventListener {
- /**
- * Called to notify merchant of payment method change. The payment app should block user
- * interaction until updateWith() or onPaymentDetailsNotUpdated().
- * https://w3c.github.io/payment-request/#paymentmethodchangeevent-interface
- *
- * @param methodName Method name. For example, "https://google.com/pay". Should not
- * be null or empty.
- * @param stringifiedDetails JSON-serialized object. For example, {"type": "debit"}. Should
- * not be null.
- * @return Whether the payment state was valid.
- */
- boolean changePaymentMethodFromInvokedApp(String methodName, String stringifiedDetails);
-
- /**
- * Called to notify merchant of shipping option change. The payment app should block user
- * interaction until updateWith() or onPaymentDetailsNotUpdated().
- * https://w3c.github.io/payment-request/#dom-paymentrequestupdateevent
- *
- * @param shippingOptionId Selected shipping option Identifier, Should not be null or
- * empty.
- * @return Whether the payment state wa valid.
- */
- boolean changeShippingOptionFromInvokedApp(String shippingOptionId);
-
- /**
- * Called to notify merchant of shipping address change. The payment app should block user
- * interaction until updateWith() or onPaymentDetailsNotUpdated().
- * https://w3c.github.io/payment-request/#dom-paymentrequestupdateevent
- *
- * @param shippingAddress Selected shipping address. Should not be null.
- * @return Whether the payment state wa valid.
- */
- boolean changeShippingAddressFromInvokedApp(PaymentAddress shippingAddress);
- }
-
- /**
* The interface for the requester of payment details from the app.
*/
public interface InstrumentDetailsCallback {
@@ -284,11 +242,9 @@ public abstract class PaymentApp extends EditableOption {
/**
* Abort invocation of the payment app.
- *
- * @param id The unique identifier of the PaymentRequest.
* @param callback The callback to return abort result.
*/
- public void abortPaymentApp(String id, AbortCallback callback) {
+ public void abortPaymentApp(AbortCallback callback) {
PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
@Override
public void run() {
@@ -300,20 +256,11 @@ public abstract class PaymentApp extends EditableOption {
/** Cleans up any resources held by the payment app. For example, closes server connections. */
public abstract void dismissInstrument();
- /** @param readyForMnimalUI Whether the payment app is ready for minimal UI flow. */
- public void setIsReadyForMinimalUI(boolean isReadyForMinimalUI) {}
-
/** @return Whether the payment app is ready for a minimal UI flow. */
public boolean isReadyForMinimalUI() {
return false;
}
- /**
- * @param accountBalance The account balance of the payment handler that is ready for a minimal
- * UI flow.
- */
- public void setAccountBalance(@Nullable String accountBalance) {}
-
/** @return Account balance for minimal UI flow. */
@Nullable
public String accountBalance() {
@@ -347,4 +294,16 @@ public abstract class PaymentApp extends EditableOption {
public long getUkmSourceId() {
return 0;
}
+
+ /**
+ * Sets the endpoint for payment handler communication. Must be called before invoking this
+ * payment app. Used only by payment apps that are backed by a payment handler.
+ * @param host The endpoint for payment handler communication. Should not be null.
+ */
+ public void setPaymentHandlerHost(PaymentHandlerHost host) {}
+
+ /** @return The type of payment app. */
+ public @PaymentAppType int getPaymentAppType() {
+ return PaymentAppType.UNDEFINED;
+ }
}
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentDetailsUpdateService.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentDetailsUpdateService.java
new file mode 100644
index 00000000000..c5e92262e2b
--- /dev/null
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentDetailsUpdateService.java
@@ -0,0 +1,76 @@
+// 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 android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+
+import org.chromium.base.task.PostTask;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
+
+/**
+ * A bound service responsible for receiving change payment method, shipping option, and shipping
+ * address calls from an inoked native payment app.
+ */
+public class PaymentDetailsUpdateService extends Service {
+ // AIDL calls can happen on multiple threads in parallel. The binder uses PostTask for
+ // synchronization since locks are discouraged in Chromium. The UI thread task runner is used
+ // rather than a SequencedTaskRunner since the state of the helper class is also changed by
+ // PaymentRequestImpl.java, which runs on the UI thread.
+ private final IPaymentDetailsUpdateService.Stub mBinder =
+ new IPaymentDetailsUpdateService.Stub() {
+ @Override
+ public void changePaymentMethod(Bundle paymentHandlerMethodData,
+ IPaymentDetailsUpdateServiceCallback callback) {
+ int callingUid = Binder.getCallingUid();
+ PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, () -> {
+ if (!PaymentDetailsUpdateServiceHelper.getInstance().isCallerAuthorized(
+ callingUid)) {
+ return;
+ }
+ PaymentDetailsUpdateServiceHelper.getInstance().changePaymentMethod(
+ paymentHandlerMethodData, callback);
+ });
+ }
+ @Override
+ public void changeShippingOption(
+ String shippingOptionId, IPaymentDetailsUpdateServiceCallback callback) {
+ int callingUid = Binder.getCallingUid();
+ PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, () -> {
+ if (!PaymentDetailsUpdateServiceHelper.getInstance().isCallerAuthorized(
+ callingUid)) {
+ return;
+ }
+ PaymentDetailsUpdateServiceHelper.getInstance().changeShippingOption(
+ shippingOptionId, callback);
+ });
+ }
+ @Override
+ public void changeShippingAddress(
+ Bundle shippingAddress, IPaymentDetailsUpdateServiceCallback callback) {
+ int callingUid = Binder.getCallingUid();
+ PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, () -> {
+ if (!PaymentDetailsUpdateServiceHelper.getInstance().isCallerAuthorized(
+ callingUid)) {
+ return;
+ }
+ PaymentDetailsUpdateServiceHelper.getInstance().changeShippingAddress(
+ shippingAddress, callback);
+ });
+ }
+ };
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (!PaymentFeatureList.isEnabledOrExperimentalFeaturesEnabled(
+ PaymentFeatureList.ANDROID_APP_PAYMENT_UPDATE_EVENTS)) {
+ return null;
+ }
+ return mBinder;
+ }
+}
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentDetailsUpdateServiceHelper.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentDetailsUpdateServiceHelper.java
new file mode 100644
index 00000000000..9ba256556c4
--- /dev/null
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentDetailsUpdateServiceHelper.java
@@ -0,0 +1,245 @@
+// 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 android.content.pm.PackageInfo;
+import android.content.pm.Signature;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.text.TextUtils;
+
+import androidx.annotation.Nullable;
+
+import org.chromium.base.Log;
+import org.chromium.base.ThreadUtils;
+import org.chromium.components.payments.intent.WebPaymentIntentHelperType.PaymentHandlerMethodData;
+import org.chromium.components.payments.intent.WebPaymentIntentHelperType.PaymentRequestDetailsUpdate;
+
+import java.util.Arrays;
+
+/**
+ * Helper class used by android payment app to notify the browser that the user has selected a
+ * different payment instrument, shipping option, or shipping address inside native app.
+ */
+public class PaymentDetailsUpdateServiceHelper {
+ private static final String TAG = "PaymentDetailsUpdate";
+
+ @Nullable
+ private IPaymentDetailsUpdateServiceCallback mCallback;
+ @Nullable
+ private PaymentRequestUpdateEventListener mListener;
+ @Nullable
+ private PackageInfo mInvokedAppPackageInfo;
+ @Nullable
+ private PackageManagerDelegate mPackageManagerDelegate;
+
+ // Singleton instance.
+ private static PaymentDetailsUpdateServiceHelper sInstance;
+ private PaymentDetailsUpdateServiceHelper(){};
+
+ /**
+ * Returns the singleton instance, lazily creating one if needed.
+ * The instance is only useful after its listener is set which happens when a native android app
+ * gets invoked.
+ * @return The singleton instance.
+ */
+ public static PaymentDetailsUpdateServiceHelper getInstance() {
+ ThreadUtils.assertOnUiThread();
+ if (sInstance == null) sInstance = new PaymentDetailsUpdateServiceHelper();
+ return sInstance;
+ }
+
+ /**
+ * Initializes the service helper, called when an AndroidPaymentApp is invoked.
+ * @param packageManagerDelegate The package manager used used to authorize the connecting app.
+ * @param invokedAppPackageName The package name of the invoked payment app, used to authorize
+ * the connecting app.
+ * @param listener The listener for payment method, shipping address, and shipping option
+ * changes.
+ */
+ public void initialize(PackageManagerDelegate packageManagerDelegate,
+ String invokedAppPackageName, PaymentRequestUpdateEventListener listener) {
+ ThreadUtils.assertOnUiThread();
+ assert mListener == null;
+ mListener = listener;
+ mPackageManagerDelegate = packageManagerDelegate;
+ mInvokedAppPackageInfo =
+ mPackageManagerDelegate.getPackageInfoWithSignatures(invokedAppPackageName);
+ }
+
+ /**
+ * Called to notify the merchant that the user has selected a different payment method.
+ * @param paymentHandlerMethodData The data containing the selected payment method's name and
+ * optional stringified details.
+ * @param callback The callback used to notify the invoked app about updated payment details.
+ */
+ public void changePaymentMethod(
+ Bundle paymentHandlerMethodData, IPaymentDetailsUpdateServiceCallback callback) {
+ ThreadUtils.assertOnUiThread();
+ if (paymentHandlerMethodData == null) {
+ runCallbackWithError(ErrorStrings.METHOD_DATA_REQUIRED, callback);
+ return;
+ }
+ String methodName =
+ paymentHandlerMethodData.getString(PaymentHandlerMethodData.EXTRA_METHOD_NAME);
+ if (TextUtils.isEmpty(methodName)) {
+ runCallbackWithError(ErrorStrings.METHOD_NAME_REQUIRED, callback);
+ return;
+ }
+
+ String stringifiedDetails = paymentHandlerMethodData.getString(
+ PaymentHandlerMethodData.EXTRA_STRINGIFIED_DETAILS, /*defaultValue=*/"{}");
+ if (isWaitingForPaymentDetailsUpdate() || mListener == null
+ || !mListener.changePaymentMethodFromInvokedApp(methodName, stringifiedDetails)) {
+ runCallbackWithError(ErrorStrings.INVALID_STATE, callback);
+ return;
+ }
+ mCallback = callback;
+ }
+
+ /**
+ * Called to notify the merchant that the user has selected a different shipping option.
+ * @param shippingOptionId The identifier of the selected shipping option.
+ * @param callback The callback used to notify the invoked app about updated payment details.
+ */
+ public void changeShippingOption(
+ String shippingOptionId, IPaymentDetailsUpdateServiceCallback callback) {
+ ThreadUtils.assertOnUiThread();
+ if (TextUtils.isEmpty(shippingOptionId)) {
+ runCallbackWithError(ErrorStrings.SHIPPING_OPTION_ID_REQUIRED, callback);
+ return;
+ }
+
+ if (isWaitingForPaymentDetailsUpdate() || mListener == null
+ || !mListener.changeShippingOptionFromInvokedApp(shippingOptionId)) {
+ runCallbackWithError(ErrorStrings.INVALID_STATE, callback);
+ return;
+ }
+ mCallback = callback;
+ }
+
+ /**
+ * Called to notify the merchant that the user has selected a different shipping address.
+ * @param shippingAddress The selected shipping address
+ * @param callback The callback used to notify the invoked app about updated payment details.
+ */
+ public void changeShippingAddress(
+ Bundle shippingAddress, IPaymentDetailsUpdateServiceCallback callback) {
+ ThreadUtils.assertOnUiThread();
+ if (shippingAddress == null || shippingAddress.isEmpty()) {
+ runCallbackWithError(ErrorStrings.SHIPPING_ADDRESS_INVALID, callback);
+ return;
+ }
+
+ Address address = Address.createFromBundle(shippingAddress);
+ if (!address.isValid()) {
+ runCallbackWithError(ErrorStrings.SHIPPING_ADDRESS_INVALID, callback);
+ return;
+ }
+
+ if (isWaitingForPaymentDetailsUpdate() || mListener == null
+ || !mListener.changeShippingAddressFromInvokedApp(
+ PaymentAddressTypeConverter.convertAddressToMojoPaymentAddress(address))) {
+ runCallbackWithError(ErrorStrings.INVALID_STATE, callback);
+ return;
+ }
+ mCallback = callback;
+ }
+
+ /**
+ * Resets the singleton instance.
+ */
+ public void reset() {
+ ThreadUtils.assertOnUiThread();
+ sInstance = null;
+ }
+
+ /**
+ * Checks whether any payment method, shipping address or shipping option change event is
+ * ongoing.
+ * @return True after invoked payment app has bound PaymentDetaialsUpdateService and called
+ * changePaymentMethod, changeShippingAddress, or changeShippingOption and before the
+ * merchant replies with either updateWith() or onPaymentDetailsNotUpdated().
+ */
+ public boolean isWaitingForPaymentDetailsUpdate() {
+ ThreadUtils.assertOnUiThread();
+ return mCallback != null;
+ }
+
+ /**
+ * Notifies the invoked app about merchant's response to the change event.
+ * @param response - Modified payment request details to be sent to the invoked app.
+ */
+ public void updateWith(PaymentRequestDetailsUpdate response) {
+ ThreadUtils.assertOnUiThread();
+ if (mCallback == null) return;
+ try {
+ mCallback.updateWith(response.asBundle());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling updateWith", e);
+ } finally {
+ mCallback = null;
+ }
+ }
+
+ /**
+ * Notfies the invoked app that the merchant has not updated any of the payment request details
+ * in response to a change event.
+ */
+ public void onPaymentDetailsNotUpdated() {
+ ThreadUtils.assertOnUiThread();
+ if (mCallback == null) return;
+ try {
+ mCallback.paymentDetailsNotUpdated();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling paymentDetailsNotUpdated", e);
+ } finally {
+ mCallback = null;
+ }
+ }
+
+ /**
+ * @param callerUid The Uid of the service requester.
+ * @return True when the service requester's package name and signature are the same as the
+ * invoked payment app's.
+ */
+ public boolean isCallerAuthorized(int callerUid) {
+ ThreadUtils.assertOnUiThread();
+ if (mPackageManagerDelegate == null) {
+ Log.e(TAG, ErrorStrings.UNATHORIZED_SERVICE_REQUEST);
+ return false;
+ }
+ PackageInfo callerPackageInfo =
+ mPackageManagerDelegate.getPackageInfoWithSignatures(callerUid);
+ if (mInvokedAppPackageInfo == null || callerPackageInfo == null
+ || !mInvokedAppPackageInfo.packageName.equals(callerPackageInfo.packageName)) {
+ Log.e(TAG, ErrorStrings.UNATHORIZED_SERVICE_REQUEST);
+ return false;
+ }
+
+ // TODO(https://crbug.com/1086485): signatures field is deprecated in API level 28.
+ Signature[] callerSignatures = callerPackageInfo.signatures;
+ Signature[] invokedAppSignatures = mInvokedAppPackageInfo.signatures;
+
+ boolean result = Arrays.equals(callerSignatures, invokedAppSignatures);
+ if (!result) Log.e(TAG, ErrorStrings.UNATHORIZED_SERVICE_REQUEST);
+ return result;
+ }
+
+ private void runCallbackWithError(
+ String errorMessage, IPaymentDetailsUpdateServiceCallback callback) {
+ ThreadUtils.assertOnUiThread();
+ if (callback == null) return;
+ // Only populate the error field.
+ Bundle blankUpdatedPaymentDetails = new Bundle();
+ blankUpdatedPaymentDetails.putString(
+ PaymentRequestDetailsUpdate.EXTRA_ERROR_MESSAGE, errorMessage);
+ try {
+ callback.updateWith(blankUpdatedPaymentDetails);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling updateWith", e);
+ }
+ }
+}
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
new file mode 100644
index 00000000000..28bf46d2499
--- /dev/null
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentFeatureList.java
@@ -0,0 +1,81 @@
+// 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.base.FeatureList;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.NativeMethods;
+
+/**
+ * Exposes payment specific features in to java since files in org.chromium.components.payments
+ * package package cannot depend on
+ * org.chromium.chrome.browser.flags.org.chromium.chrome.browser.flags.ChromeFeatureList.
+ */
+@JNINamespace("payments::android")
+public class PaymentFeatureList {
+ /** Alphabetical: */
+ public static final String ANDROID_APP_PAYMENT_UPDATE_EVENTS = "AndroidAppPaymentUpdateEvents";
+ public static final String PAYMENT_REQUEST_SKIP_TO_GPAY = "PaymentRequestSkipToGPay";
+ public static final String PAYMENT_REQUEST_SKIP_TO_GPAY_IF_NO_CARD =
+ "PaymentRequestSkipToGPayIfNoCard";
+ public static final String SCROLL_TO_EXPAND_PAYMENT_HANDLER = "ScrollToExpandPaymentHandler";
+ public static final String SERVICE_WORKER_PAYMENT_APPS = "ServiceWorkerPaymentApps";
+ public static final String STRICT_HAS_ENROLLED_AUTOFILL_INSTRUMENT =
+ "StrictHasEnrolledAutofillInstrument";
+ 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_DEBUG = "AppStoreBillingDebug";
+ public static final String WEB_PAYMENTS_EXPERIMENTAL_FEATURES =
+ "WebPaymentsExperimentalFeatures";
+ public static final String WEB_PAYMENTS_METHOD_SECTION_ORDER_V2 =
+ "WebPaymentsMethodSectionOrderV2";
+ public static final String WEB_PAYMENTS_MINIMAL_UI = "WebPaymentsMinimalUI";
+ public static final String WEB_PAYMENTS_MODIFIERS = "WebPaymentsModifiers";
+ public static final String WEB_PAYMENTS_REDACT_SHIPPING_ADDRESS =
+ "WebPaymentsRedactShippingAddress";
+ public static final String WEB_PAYMENTS_RETURN_GOOGLE_PAY_IN_BASIC_CARD =
+ "ReturnGooglePayInBasicCard";
+ public static final String WEB_PAYMENTS_SINGLE_APP_UI_SKIP = "WebPaymentsSingleAppUiSkip";
+
+ // Do not instantiate this class.
+ private PaymentFeatureList() {}
+
+ /**
+ * Returns whether the specified feature is enabled or not.
+ *
+ * Note: Features queried through this API must be added to the array
+ * |kFeaturesExposedToJava| in components/payments/content/android/payment_feature_list.cc
+ *
+ * @param featureName The name of the feature to query.
+ * @return Whether the feature is enabled or not.
+ */
+ public static boolean isEnabled(String featureName) {
+ assert FeatureList.isNativeInitialized();
+ return PaymentFeatureListJni.get().isEnabled(featureName);
+ }
+
+ /**
+ * Returns whether the feature is enabled or not. *
+ * Note: Features queried through this API must be added to the array
+ * |kFeaturesExposedToJava| in components/payments/content/android/payment_feature_list.cc
+ *
+ * @param featureName The name of the feature to query.
+ * @return true when either the specified feature or |WEB_PAYMENTS_EXPERIMENTAL_FEATURES| is
+ * enabled.
+ */
+ public static boolean isEnabledOrExperimentalFeaturesEnabled(String featureName) {
+ return isEnabled(WEB_PAYMENTS_EXPERIMENTAL_FEATURES) || isEnabled(featureName);
+ }
+
+ /**
+ * The interface implemented by the automatically generated JNI bindings class
+ * PaymentsFeatureListJni.
+ */
+ @NativeMethods
+ /* package */ interface Natives {
+ boolean isEnabled(String featureName);
+ }
+}
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentHandlerHost.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentHandlerHost.java
index 1f4d531c966..393c131bdf8 100644
--- a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentHandlerHost.java
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentHandlerHost.java
@@ -8,7 +8,6 @@ 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.PaymentAddress;
import org.chromium.payments.mojom.PaymentRequestDetailsUpdate;
import java.nio.ByteBuffer;
@@ -19,42 +18,6 @@ import java.nio.ByteBuffer;
*/
@JNINamespace("payments::android")
public class PaymentHandlerHost {
- /**
- * The interface to be implemented by the object that can communicate to the merchant renderer
- * process.
- */
- public interface PaymentHandlerHostDelegate {
- /**
- * Notifies the merchant that the payment method has changed within a payment handler. The
- * merchant may recalculate the total based on the changed billing address, for example.
- * @param methodName The payment method identifier.
- * @param stringifiedData The stringified method-specific data.
- * @return "False" if not in a valid state.
- */
- @CalledByNative("PaymentHandlerHostDelegate")
- boolean changePaymentMethodFromPaymentHandler(String methodName, String stringifiedData);
-
- /**
- * Notifies the merchant that the selected shipping option has changed within a payment
- * handler. The merchant may recalculate the payment details (e.g. total) based on the
- * updated shipping option.
- * @param shippingOptionId The selected shipping option identifier.
- * @return "False" if not in a valid state.
- */
- @CalledByNative("PaymentHandlerHostDelegate")
- boolean changeShippingOptionFromPaymentHandler(String shippingOptionId);
-
- /**
- * Notifies the merchant that the selected shipping address has changed within a payment
- * handler. The merchant may recalculate the payment details (e.g. total or shipping
- * options) based on the updated shipping address.
- * @param shippingAddress The selected shipping address.
- * @return "False" if not in a valid state.
- */
- @CalledByNative("PaymentHandlerHostDelegate")
- boolean changeShippingAddressFromPaymentHandler(PaymentAddress shippingAddress);
- }
-
/** Pointer to the native bridge. This Java object owns the native bridge. */
private long mNativePointer;
@@ -63,10 +26,10 @@ public class PaymentHandlerHost {
* bridge. The caller must call destroy() when finished using this Java object.
* @param webContents The web contents in the same browser context as the payment handler. Used
* for logging in developer tools.
- * @param delegate The object that can communicate to the merchant renderer process.
+ * @param listener The object that can communicate to the merchant renderer process.
*/
- public PaymentHandlerHost(WebContents webContents, PaymentHandlerHostDelegate delegate) {
- mNativePointer = PaymentHandlerHostJni.get().init(webContents, delegate);
+ public PaymentHandlerHost(WebContents webContents, PaymentRequestUpdateEventListener listener) {
+ mNativePointer = PaymentHandlerHostJni.get().init(webContents, listener);
}
/**
@@ -81,13 +44,13 @@ public class PaymentHandlerHost {
}
/**
- * Returns the pointer to the native payment handler host object. The native bridge owns this
- * object.
- * @return The pointer to the native payments::PaymentHandlerHost (not the native bridge
- * payments::android::PaymentHandlerHost).
+ * Returns the pointer to the native bridge. The Java object owns this bridge.
+ * @return The pointer to the native bridge payments::android::PaymentHandlerHost (not the
+ * cross-platform payment handler host payments::PaymentHandlerHost).
*/
- public long getNativePaymentHandlerHost() {
- return PaymentHandlerHostJni.get().getNativePaymentHandlerHost(mNativePointer);
+ @CalledByNative
+ public long getNativeBridge() {
+ return mNativePointer;
}
/**
@@ -114,24 +77,6 @@ public class PaymentHandlerHost {
mNativePointer = 0;
}
- @CalledByNative
- private static Object createShippingAddress(String country, String[] addressLine, String region,
- String city, String dependentLocality, String postalCode, String sortingCode,
- String organization, String recipient, String phone) {
- PaymentAddress result = new PaymentAddress();
- result.country = country;
- result.addressLine = addressLine;
- result.region = region;
- result.city = city;
- result.dependentLocality = dependentLocality;
- result.postalCode = postalCode;
- result.sortingCode = sortingCode;
- result.organization = organization;
- result.recipient = recipient;
- result.phone = phone;
- return result;
- }
-
/**
* The interface implemented by the automatically generated JNI bindings class
* PaymentHandlerHostJni.
@@ -143,10 +88,10 @@ public class PaymentHandlerHost {
* call destroy(nativePaymentHandlerHost) when done.
* @param webContents The web contents in the same browser context as the payment handler.
* Used for logging in developer tools.
- * @param delegate The object that can communicate to the merchant renderer process.
+ * @param listener The object that can communicate to the merchant renderer process.
* @return The pointer to the native payment handler host bridge.
*/
- long init(WebContents webContents, PaymentHandlerHostDelegate delegate);
+ long init(WebContents webContents, PaymentRequestUpdateEventListener listener);
/**
* Checks whether any payment method, shipping address, or shipping option change is
@@ -156,14 +101,6 @@ public class PaymentHandlerHost {
boolean isWaitingForPaymentDetailsUpdate(long nativePaymentHandlerHost);
/**
- * Returns the native pointer to the payment handler host (not the bridge). The native
- * bridge owns the returned pointer.
- * @param nativePaymentHandlerHost The pointer to the native payment handler host bridge.
- * @return The pointer to the native payment handler host.
- */
- long getNativePaymentHandlerHost(long nativePaymentHandlerHost);
-
- /**
* Notifies the payment handler that the merchant has updated the payment details.
* @param nativePaymentHandlerHost The pointer to the native payment handler host bridge.
* @param responseBuffer The serialized payment method change response from the merchant.
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestSpec.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestSpec.java
new file mode 100644
index 00000000000..8d1616f1e58
--- /dev/null
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestSpec.java
@@ -0,0 +1,79 @@
+// 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.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.NativeMethods;
+import org.chromium.payments.mojom.PaymentDetails;
+import org.chromium.payments.mojom.PaymentMethodData;
+import org.chromium.payments.mojom.PaymentOptions;
+import org.chromium.payments.mojom.PaymentValidationErrors;
+
+import java.nio.ByteBuffer;
+import java.util.Collection;
+
+/**
+ * Container for information received from the renderer that invoked the Payment Request API. Owns
+ * an instance of native payment_request_spec.cc, so the destroy() method has to be called to free
+ * the native pointer.
+ */
+@JNINamespace("payments::android")
+public class PaymentRequestSpec {
+ private long mNativePointer;
+
+ /**
+ * Stores the information received from the renderer that invoked the Payment Request API.
+ * Creates an instance of native payment_request_spec.cc with the given parameters.
+ * @param options The payment options, e.g., whether shipping is requested.
+ * @param details The payment details, e.g., the total amount.
+ * @param methodData The list of supported payment method identifiers and corresponding payment
+ * method specific data.
+ * @param appLocale The current application locale.
+ */
+ public PaymentRequestSpec(PaymentOptions options, PaymentDetails details,
+ Collection<PaymentMethodData> methodData, String appLocale) {
+ mNativePointer = PaymentRequestSpecJni.get().create(options.serialize(),
+ details.serialize(), MojoStructCollection.serialize(methodData), appLocale);
+ }
+
+ /**
+ * Called when the renderer updates the payment details in response to, e.g., new shipping
+ * address.
+ * @param details The updated payment details, e.g., the updated total amount.
+ */
+ public void updateWith(PaymentDetails details) {
+ PaymentRequestSpecJni.get().updateWith(mNativePointer, details.serialize());
+ }
+
+ /**
+ * Called when merchant retries a failed payment.
+ * @param validationErrors The information about the fields that failed the validation.
+ */
+ public void retry(PaymentValidationErrors validationErrors) {
+ PaymentRequestSpecJni.get().retry(mNativePointer, validationErrors.serialize());
+ }
+
+ /** Destroys the native pointer. */
+ public void destroy() {
+ if (mNativePointer == 0) return;
+ PaymentRequestSpecJni.get().destroy(mNativePointer);
+ mNativePointer = 0;
+ }
+
+ @CalledByNative
+ private long getNativePointer() {
+ return mNativePointer;
+ }
+
+ @NativeMethods
+ /* package */ interface Natives {
+ long create(ByteBuffer optionsByteBuffer, ByteBuffer detailsByteBuffer,
+ ByteBuffer[] methodDataByteBuffers, String appLocale);
+ void updateWith(long nativePaymentRequestSpec, ByteBuffer detailsByteBuffer);
+ void retry(long nativePaymentRequestSpec, ByteBuffer validationErrorsByteBuffer);
+ void destroy(long nativePaymentRequestSpec);
+ }
+}
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestUpdateEventListener.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestUpdateEventListener.java
new file mode 100644
index 00000000000..0df1ec56672
--- /dev/null
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestUpdateEventListener.java
@@ -0,0 +1,68 @@
+// 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.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.payments.mojom.PaymentAddress;
+
+import java.nio.ByteBuffer;
+
+/**
+ * The interface for listener to payment method, shipping address, and shipping option change
+ * events. Note: What the spec calls "payment methods" in the context of a "change event", this
+ * code calls "apps".
+ */
+@JNINamespace("payments::android")
+public interface PaymentRequestUpdateEventListener {
+ /**
+ * Called to notify merchant of payment method change. The payment app should block user
+ * interaction until updateWith() or onPaymentDetailsNotUpdated().
+ * https://w3c.github.io/payment-request/#paymentmethodchangeevent-interface
+ *
+ * @param methodName Method name. For example, "https://google.com/pay". Should not
+ * be null or empty.
+ * @param stringifiedDetails JSON-serialized object. For example, {"type": "debit"}. Should
+ * not be null.
+ * @return Whether the payment state was valid.
+ */
+ @CalledByNative
+ boolean changePaymentMethodFromInvokedApp(String methodName, String stringifiedDetails);
+
+ /**
+ * Called to notify merchant of shipping option change. The payment app should block user
+ * interaction until updateWith() or onPaymentDetailsNotUpdated().
+ * https://w3c.github.io/payment-request/#dom-paymentrequestupdateevent
+ *
+ * @param shippingOptionId Selected shipping option Identifier, Should not be null or
+ * empty.
+ * @return Whether the payment state was valid.
+ */
+ @CalledByNative
+ boolean changeShippingOptionFromInvokedApp(String shippingOptionId);
+
+ /**
+ * Called to notify merchant of shipping address change. The payment app should block user
+ * interaction until updateWith() or onPaymentDetailsNotUpdated().
+ * https://w3c.github.io/payment-request/#dom-paymentrequestupdateevent
+ *
+ * @param shippingAddress Selected shipping address. Should not be null.
+ * @return Whether the payment state was valid.
+ */
+ boolean changeShippingAddressFromInvokedApp(PaymentAddress shippingAddress);
+
+ /**
+ * Called to notify merchant of shipping address change. The payment app should block user
+ * interaction until updateWith() or onPaymentDetailsNotUpdated().
+ * https://w3c.github.io/payment-request/#dom-paymentrequestupdateevent
+ *
+ * @param shippingAddress Selected shipping address in serialized form. Should not be null.
+ * @return Whether the payment state was valid.
+ */
+ @CalledByNative
+ default boolean changeShippingAddress(ByteBuffer shippingAddress) {
+ return changeShippingAddressFromInvokedApp(PaymentAddress.deserialize(shippingAddress));
+ }
+}
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/IsReadyToPayServiceHelper.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/IsReadyToPayServiceHelper.java
index 8d8be4aee39..b7428536a24 100644
--- a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/IsReadyToPayServiceHelper.java
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/IsReadyToPayServiceHelper.java
@@ -31,6 +31,7 @@ public class IsReadyToPayServiceHelper
private boolean mIsServiceBindingInitiated;
private boolean mIsReadyToPayQueried;
private Handler mHandler;
+ private Intent mIsReadyToPayIntent;
/** The callback that returns the result (success or error) to the helper's caller. */
public interface ResultHandler {
@@ -45,8 +46,7 @@ public class IsReadyToPayServiceHelper
}
/**
- * The constructor starts the IsReadyToPay service. The result would be returned asynchronously
- * with one callback.
+ * Initiate the helper.
* @param context The application context. Should not be null.
* @param isReadyToPayIntent The IsReaddyToPay intent created by {@link
* WebPaymentIntentHelper#createIsReadyToPayIntent}. Should not be null.
@@ -60,6 +60,14 @@ public class IsReadyToPayServiceHelper
mContext = context;
mResultHandler = resultHandler;
mHandler = new Handler();
+ mIsReadyToPayIntent = isReadyToPayIntent;
+ }
+
+ /**
+ * Query the IsReadyToPay service. The result would be returned in the resultHandler callback
+ * asynchronously. Note that resultHandler would be invoked only once.
+ */
+ public void query() {
try {
// This method returns "true if the system is in the process of bringing up a
// service that your client has permission to bind to; false if the system couldn't
@@ -68,7 +76,7 @@ public class IsReadyToPayServiceHelper
// the connection."
// https://developer.android.com/reference/android/content/Context.html#bindService(android.content.Intent,%20android.content.ServiceConnection,%20int)
mIsServiceBindingInitiated = mContext.bindService(
- isReadyToPayIntent, /*serviceConnection=*/this, Context.BIND_AUTO_CREATE);
+ mIsReadyToPayIntent, /*serviceConnection=*/this, Context.BIND_AUTO_CREATE);
} catch (SecurityException e) {
// Intentionally blank, so mIsServiceBindingInitiated is false.
}
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 b13bd0173b5..b035c031668 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
@@ -16,7 +16,6 @@ import androidx.annotation.Nullable;
import org.chromium.components.payments.Address;
import org.chromium.components.payments.ErrorStrings;
import org.chromium.components.payments.PayerData;
-import org.chromium.components.payments.intent.WebPaymentIntentHelperType.PaymentCurrencyAmount;
import org.chromium.components.payments.intent.WebPaymentIntentHelperType.PaymentDetailsModifier;
import org.chromium.components.payments.intent.WebPaymentIntentHelperType.PaymentItem;
import org.chromium.components.payments.intent.WebPaymentIntentHelperType.PaymentMethodData;
@@ -26,7 +25,6 @@ import org.chromium.components.payments.intent.WebPaymentIntentHelperType.Paymen
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -50,12 +48,12 @@ public class WebPaymentIntentHelper {
public static final String EXTRA_TOP_ORIGIN = "topLevelOrigin";
public static final String EXTRA_TOTAL = "total";
public static final String EXTRA_PAYMENT_OPTIONS = "paymentOptions";
+ public static final String EXTRA_PAYMENT_OPTIONS_REQUEST_PAYER_NAME = "requestPayerName";
+ public static final String EXTRA_PAYMENT_OPTIONS_REQUEST_PAYER_PHONE = "requestPayerPhone";
+ public static final String EXTRA_PAYMENT_OPTIONS_REQUEST_PAYER_EMAIL = "requestPayerEmail";
+ public static final String EXTRA_PAYMENT_OPTIONS_REQUEST_SHIPPING = "requestShipping";
+ public static final String EXTRA_PAYMENT_OPTIONS_SHIPPING_TYPE = "shippingType";
public static final String EXTRA_SHIPPING_OPTIONS = "shippingOptions";
- public static final String EXTRA_SHIPPING_OPTION_ID = "shippingOptionId";
- public static final String EXTRA_SHIPPING_OPTION_LABEL = "label";
- public static final String EXTRA_SHIPPING_OPTION_SELECTED = "selected";
- public static final String EXTRA_SHIPPING_OPTION_AMOUNT_CURRENCY = "amountCurrency";
- public static final String EXTRA_SHIPPING_OPTION_AMOUNT_VALUE = "amountValue";
// Deprecated parameters sent to the payment app for backward compatibility.
public static final String EXTRA_DEPRECATED_CERTIFICATE_CHAIN = "certificateChain";
@@ -74,6 +72,7 @@ public class WebPaymentIntentHelper {
public static final String EXTRA_RESPONSE_PAYER_NAME = "payerName";
public static final String EXTRA_RESPONSE_PAYER_EMAIL = "payerEmail";
public static final String EXTRA_RESPONSE_PAYER_PHONE = "payerPhone";
+ public static final String EXTRA_SHIPPING_OPTION_ID = "shippingOptionId";
// Shipping address bundle used in payment response and shippingAddressChange.
public static final String EXTRA_SHIPPING_ADDRESS = "shippingAddress";
@@ -371,22 +370,24 @@ public class WebPaymentIntentHelper {
extras.putParcelable(EXTRA_METHOD_DATA, methodDataBundle);
if (modifiers != null) {
- extras.putString(EXTRA_MODIFIERS, serializeModifiers(modifiers.values()));
+ extras.putString(
+ EXTRA_MODIFIERS, PaymentDetailsModifier.serializeModifiers(modifiers.values()));
}
if (total != null) {
- String serializedTotalAmount = serializeTotalAmount(total.amount);
+ String serializedTotalAmount = total.amount.serialize();
extras.putString(EXTRA_TOTAL,
serializedTotalAmount == null ? EMPTY_JSON_DATA : serializedTotalAmount);
}
if (paymentOptions != null) {
- extras.putStringArrayList(EXTRA_PAYMENT_OPTIONS, paymentOptions.asStringArrayList());
+ extras.putBundle(EXTRA_PAYMENT_OPTIONS, buildPaymentOptionsBundle(paymentOptions));
}
// ShippingOptions are populated only when shipping is requested.
if (paymentOptions != null && paymentOptions.requestShipping) {
- Parcelable[] serializedShippingOptionList = buildShippingOptionList(shippingOptions);
+ Parcelable[] serializedShippingOptionList =
+ PaymentShippingOption.buildPaymentShippingOptionList(shippingOptions);
extras.putParcelableArray(EXTRA_SHIPPING_OPTIONS, serializedShippingOptionList);
}
@@ -436,20 +437,19 @@ public class WebPaymentIntentHelper {
return result;
}
- private static Parcelable[] buildShippingOptionList(
- List<PaymentShippingOption> shippingOptions) {
- Parcelable[] result = new Parcelable[shippingOptions.size()];
- int index = 0;
- for (PaymentShippingOption option : shippingOptions) {
- Bundle bundle = new Bundle();
- bundle.putString(EXTRA_SHIPPING_OPTION_ID, option.id);
- bundle.putString(EXTRA_SHIPPING_OPTION_LABEL, option.label);
- bundle.putString(EXTRA_SHIPPING_OPTION_AMOUNT_CURRENCY, option.amountCurrency);
- bundle.putString(EXTRA_SHIPPING_OPTION_AMOUNT_VALUE, option.amountValue);
- bundle.putBoolean(EXTRA_SHIPPING_OPTION_SELECTED, option.selected);
- result[index++] = bundle;
+ private static Bundle buildPaymentOptionsBundle(PaymentOptions paymentOptions) {
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(
+ EXTRA_PAYMENT_OPTIONS_REQUEST_PAYER_NAME, paymentOptions.requestPayerName);
+ bundle.putBoolean(
+ EXTRA_PAYMENT_OPTIONS_REQUEST_PAYER_EMAIL, paymentOptions.requestPayerEmail);
+ bundle.putBoolean(
+ EXTRA_PAYMENT_OPTIONS_REQUEST_PAYER_PHONE, paymentOptions.requestPayerPhone);
+ bundle.putBoolean(EXTRA_PAYMENT_OPTIONS_REQUEST_SHIPPING, paymentOptions.requestShipping);
+ if (paymentOptions.shippingType != null) {
+ bundle.putString(EXTRA_PAYMENT_OPTIONS_SHIPPING_TYPE, paymentOptions.shippingType);
}
- return result;
+ return bundle;
}
private static String deprecatedSerializeDetails(
@@ -463,7 +463,7 @@ public class WebPaymentIntentHelper {
if (total != null) {
// total {{{
json.name("total");
- serializeTotal(total, json);
+ total.serializeAndRedact(json);
// }}} total
}
@@ -484,87 +484,6 @@ public class WebPaymentIntentHelper {
return stringWriter.toString();
}
- private static String serializeTotalAmount(PaymentCurrencyAmount totalAmount) {
- StringWriter stringWriter = new StringWriter();
- JsonWriter json = new JsonWriter(stringWriter);
- try {
- // {{{
- json.beginObject();
- json.name("currency").value(totalAmount.currency);
- json.name("value").value(totalAmount.value);
- json.endObject();
- // }}}
- } catch (IOException e) {
- return null;
- }
- return stringWriter.toString();
- }
-
- private static void serializeTotal(PaymentItem item, JsonWriter json) throws IOException {
- // item {{{
- json.beginObject();
- // Sanitize the total name, because the payment app does not need it to complete the
- // transaction. Matches the behavior of:
- // https://w3c.github.io/payment-handler/#total-attribute
- json.name("label").value("");
-
- // amount {{{
- json.name("amount").beginObject();
- json.name("currency").value(item.amount.currency);
- json.name("value").value(item.amount.value);
- json.endObject();
- // }}} amount
-
- json.endObject();
- // }}} item
- }
-
- private static String serializeModifiers(Collection<PaymentDetailsModifier> modifiers) {
- StringWriter stringWriter = new StringWriter();
- JsonWriter json = new JsonWriter(stringWriter);
- try {
- json.beginArray();
- for (PaymentDetailsModifier modifier : modifiers) {
- checkNotNull(modifier, "PaymentDetailsModifier");
- serializeModifier(modifier, json);
- }
- json.endArray();
- } catch (IOException e) {
- return EMPTY_JSON_DATA;
- }
- return stringWriter.toString();
- }
-
- private static void serializeModifier(PaymentDetailsModifier modifier, JsonWriter json)
- throws IOException {
- // {{{
- json.beginObject();
-
- // total {{{
- if (modifier.total != null) {
- json.name("total");
- serializeTotal(modifier.total, json);
- } else {
- json.name("total").nullValue();
- }
- // }}} total
-
- // TODO(https://crbug.com/754779): The supportedMethods field was already changed from array
- // to string but we should keep backward-compatibility for now.
- // supportedMethods {{{
- json.name("supportedMethods").beginArray();
- json.value(modifier.methodData.supportedMethod);
- json.endArray();
- // }}} supportedMethods
-
- // data {{{
- json.name("data").value(modifier.methodData.stringifiedData);
- // }}}
-
- json.endObject();
- // }}}
- }
-
private static String getStringOrEmpty(Intent data, String key) {
return data.getExtras().getString(key, /*defaultValue =*/"");
}
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelperType.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelperType.java
index 14e17efde93..8ac1a1d876d 100644
--- a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelperType.java
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelperType.java
@@ -4,33 +4,79 @@
package org.chromium.components.payments.intent;
-import androidx.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.JsonWriter;
-import java.util.ArrayList;
+import androidx.annotation.Nullable;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.Collection;
+import java.util.List;
/**
* The types that corresponds to the types in org.chromium.payments.mojom. The fields of these types
* are the subset of those in the mojom types. The subset is minimally selected based on the need of
* this package. This class should be independent of the org.chromium package.
*
* @see <a
- * href="https://developers.google.com/web/fundamentals/payments/payment-apps-developer-guide/android-payment-apps#payment_parameters">Payment
+ * href="https://web.dev/android-payment-apps-overview/#parameters-2">Payment
* parameters</a>
* @see <a
- * href="https://developers.google.com/web/fundamentals/payments/payment-apps-developer-guide/android-payment-apps#%E2%80%9Cis_ready_to_pay%E2%80%9D_parameters">“Is
+ * href="https://web.dev/android-payment-apps-overview/#parameters">“Is
* ready to pay” parameters</a>
*/
public final class WebPaymentIntentHelperType {
+ private static final String EMPTY_JSON_DATA = "{}";
+
/**
* The class that corresponds to mojom.PaymentCurrencyAmount, with minimally required fields.
*/
public static final class PaymentCurrencyAmount {
+ public static String EXTRA_CURRENCY = "currency";
+ public static String EXTRA_VALUE = "value";
+
public final String currency;
public final String value;
public PaymentCurrencyAmount(String currency, String value) {
this.currency = currency;
this.value = value;
}
+
+ /**
+ * Serializes this object into the provided json writer.
+ * @param json The json object to which the seri
+ */
+ public void serialize(JsonWriter json) throws IOException {
+ // {{{
+ json.beginObject();
+ json.name("currency").value(currency);
+ json.name("value").value(value);
+ json.endObject();
+ // }}}
+ }
+ /**
+ * Serializes this object
+ * @return The serialized payment currency amount.
+ */
+ public String serialize() {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter json = new JsonWriter(stringWriter);
+ try {
+ serialize(json);
+ } catch (IOException e) {
+ return null;
+ }
+ return stringWriter.toString();
+ }
+
+ /* package */ Bundle asBundle() {
+ Bundle bundle = new Bundle();
+ bundle.putString(EXTRA_CURRENCY, currency);
+ bundle.putString(EXTRA_VALUE, value);
+ return bundle;
+ }
}
/** The class that corresponds mojom.PaymentItem, with minimally required fields. */
@@ -39,6 +85,27 @@ public final class WebPaymentIntentHelperType {
public PaymentItem(PaymentCurrencyAmount amount) {
this.amount = amount;
}
+ /**
+ * Serializes this object into the provided json writer after adding an empty string for the
+ * redacted "label" field.
+ * @param json The json writer used for serialization
+ */
+ public void serializeAndRedact(JsonWriter json) throws IOException {
+ // item {{{
+ json.beginObject();
+ // Redact the total label, because the payment app does not need it to complete the
+ // transaction. Matches the behavior of:
+ // https://w3c.github.io/payment-handler/#total-attribute
+ json.name("label").value("");
+
+ // amount {{{
+ json.name("amount");
+ amount.serialize(json);
+ // }}} amount
+
+ json.endObject();
+ // }}} item
+ }
}
/** The class that corresponds mojom.PaymentDetailsModifier, with minimally required fields. */
@@ -49,6 +116,56 @@ public final class WebPaymentIntentHelperType {
this.total = total;
this.methodData = methodData;
}
+
+ /**
+ * Serializes payment details modifiers.
+ * @param modifiers The collection of details modifiers to serialize.
+ * @return The serialized payment details modifiers
+ */
+ public static String serializeModifiers(Collection<PaymentDetailsModifier> modifiers) {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter json = new JsonWriter(stringWriter);
+ try {
+ json.beginArray();
+ for (PaymentDetailsModifier modifier : modifiers) {
+ checkNotNull(modifier, "PaymentDetailsModifier");
+ modifier.serialize(json);
+ }
+ json.endArray();
+ } catch (IOException e) {
+ return EMPTY_JSON_DATA;
+ }
+ return stringWriter.toString();
+ }
+
+ private void serialize(JsonWriter json) throws IOException {
+ // {{{
+ json.beginObject();
+
+ // total {{{
+ if (total != null) {
+ json.name("total");
+ total.serializeAndRedact(json);
+ } else {
+ json.name("total").nullValue();
+ }
+ // }}} total
+
+ // TODO(https://crbug.com/754779): The supportedMethods field was already changed from
+ // array to string but we should keep backward-compatibility for now. supportedMethods
+ // {{{
+ json.name("supportedMethods").beginArray();
+ json.value(methodData.supportedMethod);
+ json.endArray();
+ // }}} supportedMethods
+
+ // data {{{
+ json.name("data").value(methodData.stringifiedData);
+ // }}}
+
+ json.endObject();
+ // }}}
+ }
}
/** The class that corresponds mojom.PaymentMethodData, with minimally required fields. */
@@ -63,19 +180,46 @@ public final class WebPaymentIntentHelperType {
/** The class that mirrors mojom.PaymentShippingOption. */
public static final class PaymentShippingOption {
+ public static final String EXTRA_SHIPPING_OPTION_ID = "id";
+ public static final String EXTRA_SHIPPING_OPTION_LABEL = "label";
+ public static final String EXTRA_SHIPPING_OPTION_AMOUNT = "amount";
+ public static final String EXTRA_SHIPPING_OPTION_SELECTED = "selected";
+
public final String id;
public final String label;
- public final String amountCurrency;
- public final String amountValue;
+ public final PaymentCurrencyAmount amount;
public final boolean selected;
- public PaymentShippingOption(String id, String label, String amountCurrency,
- String amountValue, boolean selected) {
+ public PaymentShippingOption(
+ String id, String label, PaymentCurrencyAmount amount, boolean selected) {
this.id = id;
this.label = label;
- this.amountCurrency = amountCurrency;
- this.amountValue = amountValue;
+ this.amount = amount;
this.selected = selected;
}
+
+ private Bundle asBundle() {
+ Bundle bundle = new Bundle();
+ bundle.putString(EXTRA_SHIPPING_OPTION_ID, id);
+ bundle.putString(EXTRA_SHIPPING_OPTION_LABEL, label);
+ bundle.putBundle(EXTRA_SHIPPING_OPTION_AMOUNT, amount.asBundle());
+ bundle.putBoolean(EXTRA_SHIPPING_OPTION_SELECTED, selected);
+ return bundle;
+ }
+
+ /**
+ * Create a parcelable array of payment shipping options.
+ * @param shippingOptions The list of available shipping options
+ * @return The parcelable array of shipping options passed to the native payment app.
+ */
+ public static Parcelable[] buildPaymentShippingOptionList(
+ List<PaymentShippingOption> shippingOptions) {
+ Parcelable[] result = new Parcelable[shippingOptions.size()];
+ int index = 0;
+ for (PaymentShippingOption option : shippingOptions) {
+ result[index++] = option.asBundle();
+ }
+ return result;
+ }
}
/** The class that mirrors mojom.PaymentOptions. */
@@ -94,22 +238,85 @@ public final class WebPaymentIntentHelperType {
this.requestShipping = requestShipping;
this.shippingType = shippingType;
}
+ }
+ private static void checkNotNull(Object value, String name) {
+ if (value == null) throw new IllegalArgumentException(name + " should not be null.");
+ }
+
+ /** The class that mirrors mojom.PaymentHandlerMethodData. */
+ public static final class PaymentHandlerMethodData {
+ public static final String EXTRA_METHOD_NAME = "methodName";
+ public static final String EXTRA_STRINGIFIED_DETAILS = "details";
+
+ public final String methodName;
+ public final String stringifiedData;
+ public PaymentHandlerMethodData(String methodName, String stringifiedData) {
+ this.methodName = methodName;
+ this.stringifiedData = stringifiedData;
+ }
+
+ /* package */ Bundle asBundle() {
+ Bundle bundle = new Bundle();
+ bundle.putString(EXTRA_METHOD_NAME, methodName);
+ bundle.putString(EXTRA_STRINGIFIED_DETAILS, stringifiedData);
+ return bundle;
+ }
+ }
+
+ /** The class that mirrors mojom.PaymentRequestDetailsUpdate. */
+ public static final class PaymentRequestDetailsUpdate {
+ public static final String EXTRA_TOTAL = "total";
+ public static final String EXTRA_SHIPPING_OPTIONS = "shippingOptions";
+ public static final String EXTRA_ERROR_MESSAGE = "error";
+ public static final String EXTRA_STRINGIFIED_PAYMENT_METHOD_ERRORS =
+ "stringifiedPaymentMethodErrors";
+ public static final String EXTRA_ADDRESS_ERRORS = "addressErrors";
+
+ @Nullable
+ public final PaymentCurrencyAmount total;
+ @Nullable
+ public final List<PaymentShippingOption> shippingOptions;
+ @Nullable
+ public final String error;
+ @Nullable
+ public final String stringifiedPaymentMethodErrors;
+ @Nullable
+ public final Bundle bundledShippingAddressErrors;
+
+ public PaymentRequestDetailsUpdate(@Nullable PaymentCurrencyAmount total,
+ @Nullable List<PaymentShippingOption> shippingOptions, @Nullable String error,
+ @Nullable String stringifiedPaymentMethodErrors,
+ @Nullable Bundle bundledShippingAddressErrors) {
+ this.total = total;
+ this.shippingOptions = shippingOptions;
+ this.error = error;
+ this.stringifiedPaymentMethodErrors = stringifiedPaymentMethodErrors;
+ this.bundledShippingAddressErrors = bundledShippingAddressErrors;
+ }
/**
- * @return an ArrayList of stringified payment options. This should be an ArrayList vs a
- * List since the |Bundle.putStringArrayList()| function used for populating
- * "paymentOptions" in "Pay" intents accepts ArrayLists.
+ * Converts PaymentRequestDetailsUpdate to a bundle which will be passed to the invoked
+ * payment app.
+ * @return The converted PaymentRequestDetailsUpdate
*/
- public ArrayList<String> asStringArrayList() {
- ArrayList<String> paymentOptionList = new ArrayList<>();
- if (requestPayerName) paymentOptionList.add("requestPayerName");
- if (requestPayerEmail) paymentOptionList.add("requestPayerEmail");
- if (requestPayerPhone) paymentOptionList.add("requestPayerPhone");
- if (requestShipping) {
- paymentOptionList.add("requestShipping");
- paymentOptionList.add(shippingType);
+ public Bundle asBundle() {
+ Bundle bundle = new Bundle();
+ if (total != null) {
+ bundle.putBundle(WebPaymentIntentHelper.EXTRA_TOTAL, total.asBundle());
+ }
+ if (shippingOptions != null && !shippingOptions.isEmpty()) {
+ bundle.putParcelableArray(EXTRA_SHIPPING_OPTIONS,
+ PaymentShippingOption.buildPaymentShippingOptionList(shippingOptions));
+ }
+ if (!TextUtils.isEmpty(error)) bundle.putString(EXTRA_ERROR_MESSAGE, error);
+ if (!TextUtils.isEmpty(stringifiedPaymentMethodErrors)) {
+ bundle.putString(
+ EXTRA_STRINGIFIED_PAYMENT_METHOD_ERRORS, stringifiedPaymentMethodErrors);
+ }
+ if (bundledShippingAddressErrors != null) {
+ bundle.putBundle(EXTRA_ADDRESS_ERRORS, bundledShippingAddressErrors);
}
- return paymentOptionList;
+ return bundle;
}
}
}
diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/payment_details_update_service.aidl b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/payment_details_update_service.aidl
new file mode 100644
index 00000000000..2cae1654e69
--- /dev/null
+++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/payment_details_update_service.aidl
@@ -0,0 +1,6 @@
+// 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.
+
+interface org.chromium.components.payments.IPaymentDetailsUpdateService;
+interface org.chromium.components.payments.IPaymentDetailsUpdateServiceCallback;
diff --git a/chromium/components/payments/content/android/java_templates/ErrorStrings.java.tmpl b/chromium/components/payments/content/android/java_templates/ErrorStrings.java.tmpl
index 2ca0ff39f71..741b2cfe928 100644
--- a/chromium/components/payments/content/android/java_templates/ErrorStrings.java.tmpl
+++ b/chromium/components/payments/content/android/java_templates/ErrorStrings.java.tmpl
@@ -52,6 +52,9 @@ public abstract class ErrorStrings {{
public static final String MINIMAL_UI_SUPPRESSED = "Payment minimal UI suppressed.";
+ public static final String UNATHORIZED_SERVICE_REQUEST =
+ "Caller's signuature or package name does not match invoked app's.";
+
// Prevent instantiation.
private ErrorStrings() {{}}
}}
diff --git a/chromium/components/payments/content/android/jni_payment_app.cc b/chromium/components/payments/content/android/jni_payment_app.cc
new file mode 100644
index 00000000000..621eacc557c
--- /dev/null
+++ b/chromium/components/payments/content/android/jni_payment_app.cc
@@ -0,0 +1,254 @@
+// 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/jni_payment_app.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/android/jni_array.h"
+#include "base/android/jni_string.h"
+#include "base/callback.h"
+#include "components/payments/content/android/byte_buffer_helper.h"
+#include "components/payments/content/android/jni_headers/JniPaymentApp_jni.h"
+#include "components/payments/content/android/payment_handler_host.h"
+#include "components/payments/content/payment_request_converter.h"
+#include "components/payments/core/payment_method_data.h"
+#include "third_party/blink/public/mojom/payments/payment_request.mojom.h"
+#include "ui/gfx/android/java_bitmap.h"
+
+namespace payments {
+namespace {
+
+using ::base::android::AttachCurrentThread;
+using ::base::android::ConvertJavaStringToUTF8;
+using ::base::android::ConvertUTF16ToJavaString;
+using ::base::android::ConvertUTF8ToJavaString;
+using ::base::android::JavaParamRef;
+using ::base::android::ScopedJavaLocalRef;
+using ::base::android::ToJavaArrayOfStrings;
+
+void OnAbortResult(const ::base::android::JavaRef<jobject>& jcallback,
+ bool aborted) {
+ Java_JniPaymentApp_onAbortResult(AttachCurrentThread(), jcallback, aborted);
+}
+
+} // namespace
+
+// static
+ScopedJavaLocalRef<jobject> JniPaymentApp::Create(
+ JNIEnv* env,
+ std::unique_ptr<PaymentApp> payment_app) {
+ // The |app| is owned by JniPaymentApp.java and will be destroyed through a
+ // JniPaymentApp::FreeNativeObject() call.
+ JniPaymentApp* app = new JniPaymentApp(std::move(payment_app));
+
+ ScopedJavaLocalRef<jobject> icon;
+ if (app->payment_app_->icon_bitmap() &&
+ !app->payment_app_->icon_bitmap()->drawsNothing()) {
+ icon = gfx::ConvertToJavaBitmap(app->payment_app_->icon_bitmap());
+ }
+
+ return Java_JniPaymentApp_Constructor(
+ env, ConvertUTF8ToJavaString(env, app->payment_app_->GetId()),
+ ConvertUTF16ToJavaString(env, app->payment_app_->GetLabel()),
+ ConvertUTF16ToJavaString(env, app->payment_app_->GetSublabel()), icon,
+ static_cast<jint>(app->payment_app_->type()),
+ reinterpret_cast<jlong>(app));
+}
+
+ScopedJavaLocalRef<jobjectArray> JniPaymentApp::GetInstrumentMethodNames(
+ JNIEnv* env) {
+ return ToJavaArrayOfStrings(
+ env, std::vector<std::string>(payment_app_->GetAppMethodNames().begin(),
+ payment_app_->GetAppMethodNames().end()));
+}
+
+bool JniPaymentApp::IsValidForPaymentMethodData(
+ JNIEnv* env,
+ const JavaParamRef<jstring>& jmethod,
+ const JavaParamRef<jobject>& jdata_byte_buffer) {
+ if (!jdata_byte_buffer) {
+ bool is_valid = false;
+ payment_app_->IsValidForPaymentMethodIdentifier(
+ ConvertJavaStringToUTF8(env, jmethod), &is_valid);
+ return is_valid;
+ }
+
+ mojom::PaymentMethodDataPtr mojo_data;
+ bool success = android::DeserializeFromJavaByteBuffer(env, jdata_byte_buffer,
+ &mojo_data);
+ DCHECK(success);
+
+ PaymentMethodData data = ConvertPaymentMethodData(mojo_data);
+ return payment_app_->IsValidForModifier(
+ ConvertJavaStringToUTF8(env, jmethod), !data.supported_networks.empty(),
+ std::set<std::string>(data.supported_networks.begin(),
+ data.supported_networks.end()));
+}
+
+bool JniPaymentApp::HandlesShippingAddress(JNIEnv* env) {
+ return payment_app_->HandlesShippingAddress();
+}
+
+bool JniPaymentApp::HandlesPayerName(JNIEnv* env) {
+ return payment_app_->HandlesPayerName();
+}
+
+bool JniPaymentApp::HandlesPayerEmail(JNIEnv* env) {
+ return payment_app_->HandlesPayerEmail();
+}
+
+bool JniPaymentApp::HandlesPayerPhone(JNIEnv* env) {
+ return payment_app_->HandlesPayerPhone();
+}
+
+ScopedJavaLocalRef<jstring> JniPaymentApp::GetCountryCode(JNIEnv* env) {
+ // Only autofill payment apps have country code.
+ return nullptr;
+}
+
+bool JniPaymentApp::CanMakePayment(JNIEnv* env) {
+ // PaymentRequestImpl.java uses this value to determine whether
+ // PaymentRequest.hasEnrolledInstrument() should return true.
+ return payment_app_->HasEnrolledInstrument();
+}
+
+bool JniPaymentApp::CanPreselect(JNIEnv* env) {
+ return payment_app_->CanPreselect();
+}
+
+bool JniPaymentApp::IsUserGestureRequiredToSkipUi(JNIEnv* env) {
+ // All payment apps require a user gesture to skip UI by default.
+ return true;
+}
+
+void JniPaymentApp::InvokePaymentApp(JNIEnv* env,
+ const JavaParamRef<jobject>& jcallback) {
+ invoke_callback_ = jcallback;
+ payment_app_->InvokePaymentApp(/*delegate=*/this);
+}
+
+void JniPaymentApp::UpdateWith(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jresponse_byte_buffer) {
+ mojom::PaymentRequestDetailsUpdatePtr response;
+ bool success = android::DeserializeFromJavaByteBuffer(
+ env, jresponse_byte_buffer, &response);
+ DCHECK(success);
+ payment_app_->UpdateWith(std::move(response));
+}
+
+void JniPaymentApp::OnPaymentDetailsNotUpdated(JNIEnv* env) {
+ payment_app_->OnPaymentDetailsNotUpdated();
+}
+
+bool JniPaymentApp::IsWaitingForPaymentDetailsUpdate(JNIEnv* env) {
+ return payment_app_->IsWaitingForPaymentDetailsUpdate();
+}
+
+void JniPaymentApp::AbortPaymentApp(JNIEnv* env,
+ const JavaParamRef<jobject>& jcallback) {
+ payment_app_->AbortPaymentApp(base::BindOnce(
+ &OnAbortResult,
+ base::android::ScopedJavaGlobalRef<jobject>(env, jcallback)));
+}
+
+bool JniPaymentApp::IsReadyForMinimalUI(JNIEnv* env) {
+ return payment_app_->IsReadyForMinimalUI();
+}
+
+ScopedJavaLocalRef<jstring> JniPaymentApp::AccountBalance(JNIEnv* env) {
+ return ConvertUTF8ToJavaString(env, payment_app_->GetAccountBalance());
+}
+
+void JniPaymentApp::DisableShowingOwnUI(JNIEnv* env) {
+ payment_app_->DisableShowingOwnUI();
+}
+
+ScopedJavaLocalRef<jstring> JniPaymentApp::GetApplicationIdentifierToHide(
+ JNIEnv* env) {
+ return ConvertUTF8ToJavaString(
+ env, payment_app_->GetApplicationIdentifierToHide());
+}
+
+ScopedJavaLocalRef<jobjectArray>
+JniPaymentApp::GetApplicationIdentifiersThatHideThisApp(JNIEnv* env) {
+ const std::set<std::string>& ids =
+ payment_app_->GetApplicationIdentifiersThatHideThisApp();
+ return ToJavaArrayOfStrings(env,
+ std::vector<std::string>(ids.begin(), ids.end()));
+}
+
+jlong JniPaymentApp::GetUkmSourceId(JNIEnv* env) {
+ return payment_app_->UkmSourceId();
+}
+
+void JniPaymentApp::SetPaymentHandlerHost(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jpayment_handler_host) {
+ payment_app_->SetPaymentHandlerHost(
+ android::PaymentHandlerHost::FromJavaPaymentHandlerHost(
+ env, jpayment_handler_host));
+}
+
+void JniPaymentApp::FreeNativeObject(JNIEnv* env) {
+ delete this;
+}
+
+void JniPaymentApp::OnInstrumentDetailsReady(
+ const std::string& method_name,
+ const std::string& stringified_details,
+ const PayerData& payer_data) {
+ JNIEnv* env = AttachCurrentThread();
+
+ ScopedJavaLocalRef<jobject> jshipping_address =
+ payer_data.shipping_address
+ ? Java_JniPaymentApp_createShippingAddress(
+ env,
+ ConvertUTF8ToJavaString(env,
+ payer_data.shipping_address->country),
+ ToJavaArrayOfStrings(env,
+ payer_data.shipping_address->address_line),
+ ConvertUTF8ToJavaString(env,
+ payer_data.shipping_address->region),
+ ConvertUTF8ToJavaString(env, payer_data.shipping_address->city),
+ ConvertUTF8ToJavaString(
+ env, payer_data.shipping_address->dependent_locality),
+ ConvertUTF8ToJavaString(
+ env, payer_data.shipping_address->postal_code),
+ ConvertUTF8ToJavaString(
+ env, payer_data.shipping_address->sorting_code),
+ ConvertUTF8ToJavaString(
+ env, payer_data.shipping_address->organization),
+ ConvertUTF8ToJavaString(env,
+ payer_data.shipping_address->recipient),
+ ConvertUTF8ToJavaString(env,
+ payer_data.shipping_address->phone))
+ : nullptr;
+
+ ScopedJavaLocalRef<jobject> jpayer_data = Java_JniPaymentApp_createPayerData(
+ env, ConvertUTF8ToJavaString(env, payer_data.payer_name),
+ ConvertUTF8ToJavaString(env, payer_data.payer_phone),
+ ConvertUTF8ToJavaString(env, payer_data.payer_email), jshipping_address,
+ ConvertUTF8ToJavaString(env, payer_data.selected_shipping_option_id));
+
+ Java_JniPaymentApp_onInvokeResult(
+ env, invoke_callback_, ConvertUTF8ToJavaString(env, method_name),
+ ConvertUTF8ToJavaString(env, stringified_details), jpayer_data);
+}
+
+void JniPaymentApp::OnInstrumentDetailsError(const std::string& error_message) {
+ JNIEnv* env = AttachCurrentThread();
+ Java_JniPaymentApp_onInvokeError(env, invoke_callback_,
+ ConvertUTF8ToJavaString(env, error_message));
+}
+
+JniPaymentApp::JniPaymentApp(std::unique_ptr<PaymentApp> payment_app)
+ : payment_app_(std::move(payment_app)) {}
+
+JniPaymentApp::~JniPaymentApp() = default;
+
+} // namespace payments
diff --git a/chromium/components/payments/content/android/jni_payment_app.h b/chromium/components/payments/content/android/jni_payment_app.h
new file mode 100644
index 00000000000..16ffaced8f1
--- /dev/null
+++ b/chromium/components/payments/content/android/jni_payment_app.h
@@ -0,0 +1,101 @@
+// 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_JNI_PAYMENT_APP_H_
+#define COMPONENTS_PAYMENTS_CONTENT_ANDROID_JNI_PAYMENT_APP_H_
+
+#include <jni.h>
+#include <memory>
+
+#include "base/android/scoped_java_ref.h"
+#include "components/payments/content/payment_app.h"
+
+namespace payments {
+
+// Forwarding calls to a PaymentApp. Owned by JniPaymentApp.java.
+class JniPaymentApp : public PaymentApp::Delegate {
+ public:
+ static base::android::ScopedJavaLocalRef<jobject> Create(
+ JNIEnv* env,
+ std::unique_ptr<PaymentApp> payment_app);
+
+ // Disallow copy and assign.
+ JniPaymentApp(const JniPaymentApp& other) = delete;
+ JniPaymentApp& operator=(const JniPaymentApp& other) = delete;
+
+ base::android::ScopedJavaLocalRef<jobjectArray> GetInstrumentMethodNames(
+ JNIEnv* env);
+
+ bool IsValidForPaymentMethodData(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jstring>& jmethod,
+ const base::android::JavaParamRef<jobject>& jdata_byte_buffer);
+
+ bool HandlesShippingAddress(JNIEnv* env);
+
+ bool HandlesPayerName(JNIEnv* env);
+
+ bool HandlesPayerEmail(JNIEnv* env);
+
+ bool HandlesPayerPhone(JNIEnv* env);
+
+ base::android::ScopedJavaLocalRef<jstring> GetCountryCode(JNIEnv* env);
+
+ bool CanMakePayment(JNIEnv* env);
+
+ bool CanPreselect(JNIEnv* env);
+
+ bool IsUserGestureRequiredToSkipUi(JNIEnv* env);
+
+ void InvokePaymentApp(JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& jcallback);
+
+ void UpdateWith(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& jresponse_byte_buffer);
+
+ void OnPaymentDetailsNotUpdated(JNIEnv* env);
+
+ bool IsWaitingForPaymentDetailsUpdate(JNIEnv* env);
+
+ void AbortPaymentApp(JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& jcallback);
+
+ bool IsReadyForMinimalUI(JNIEnv* env);
+
+ base::android::ScopedJavaLocalRef<jstring> AccountBalance(JNIEnv* env);
+
+ void DisableShowingOwnUI(JNIEnv* env);
+
+ base::android::ScopedJavaLocalRef<jstring> GetApplicationIdentifierToHide(
+ JNIEnv* env);
+
+ base::android::ScopedJavaLocalRef<jobjectArray>
+ GetApplicationIdentifiersThatHideThisApp(JNIEnv* env);
+
+ jlong GetUkmSourceId(JNIEnv* env);
+
+ void SetPaymentHandlerHost(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& jpayment_handler_host);
+
+ void FreeNativeObject(JNIEnv* env);
+
+ private:
+ // PaymentApp::Delegate implementation:
+ void OnInstrumentDetailsReady(const std::string& method_name,
+ const std::string& stringified_details,
+ const PayerData& payer_data) override;
+ void OnInstrumentDetailsError(const std::string& error_message) override;
+
+ explicit JniPaymentApp(std::unique_ptr<PaymentApp> payment_app);
+ ~JniPaymentApp() override;
+
+ std::unique_ptr<PaymentApp> payment_app_;
+ base::android::ScopedJavaGlobalRef<jobject> invoke_callback_;
+};
+
+} // namespace payments
+
+#endif // COMPONENTS_PAYMENTS_CONTENT_ANDROID_JNI_PAYMENT_APP_H_
diff --git a/chromium/components/payments/content/android/payment_feature_list.cc b/chromium/components/payments/content/android/payment_feature_list.cc
new file mode 100644
index 00000000000..c21c16bac3b
--- /dev/null
+++ b/chromium/components/payments/content/android/payment_feature_list.cc
@@ -0,0 +1,69 @@
+// 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_feature_list.h"
+
+#include "base/android/jni_string.h"
+#include "base/feature_list.h"
+#include "base/notreached.h"
+#include "components/payments/content/android/jni_headers/PaymentFeatureList_jni.h"
+#include "components/payments/core/features.h"
+#include "content/public/common/content_features.h"
+
+namespace payments {
+namespace android {
+namespace {
+
+// Array of payment features exposed through the Java PaymentFeatureList API.
+// Entries in this array refer to features defined in
+// components/payments/core/features.h, content/public/common/content_features.h
+// or the .h file (for Android only features).
+const base::Feature* kFeaturesExposedToJava[] = {
+ &::features::kServiceWorkerPaymentApps,
+ &::features::kWebPayments,
+ &::features::kWebPaymentsMinimalUI,
+ &features::kAlwaysAllowJustInTimePaymentApp,
+ &features::kAppStoreBillingDebug,
+ &features::kPaymentRequestSkipToGPay,
+ &features::kPaymentRequestSkipToGPayIfNoCard,
+ &features::kReturnGooglePayInBasicCard,
+ &features::kStrictHasEnrolledAutofillInstrument,
+ &features::kWebPaymentsExperimentalFeatures,
+ &features::kWebPaymentsMethodSectionOrderV2,
+ &features::kWebPaymentsModifiers,
+ &features::kWebPaymentsRedactShippingAddress,
+ &features::kWebPaymentsSingleAppUiSkip,
+ &kAndroidAppPaymentUpdateEvents,
+ &kScrollToExpandPaymentHandler,
+};
+
+const base::Feature* FindFeatureExposedToJava(const std::string& feature_name) {
+ for (size_t i = 0; i < base::size(kFeaturesExposedToJava); ++i) {
+ if (kFeaturesExposedToJava[i]->name == feature_name)
+ return kFeaturesExposedToJava[i];
+ }
+ NOTREACHED() << "Queried feature cannot be found in PaymentsFeatureList: "
+ << feature_name;
+ return nullptr;
+}
+
+} // namespace
+
+// Android only features.
+const base::Feature kAndroidAppPaymentUpdateEvents{
+ "AndroidAppPaymentUpdateEvents", base::FEATURE_ENABLED_BY_DEFAULT};
+// TODO(crbug.com/1094549): clean up after being stable.
+const base::Feature kScrollToExpandPaymentHandler{
+ "ScrollToExpandPaymentHandler", base::FEATURE_ENABLED_BY_DEFAULT};
+
+static jboolean JNI_PaymentFeatureList_IsEnabled(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jstring>& jfeature_name) {
+ const base::Feature* feature = FindFeatureExposedToJava(
+ base::android::ConvertJavaStringToUTF8(env, jfeature_name));
+ return base::FeatureList::IsEnabled(*feature);
+}
+
+} // namespace android
+} // namespace payments
diff --git a/chromium/components/payments/content/android/payment_feature_list.h b/chromium/components/payments/content/android/payment_feature_list.h
new file mode 100644
index 00000000000..2d10c369def
--- /dev/null
+++ b/chromium/components/payments/content/android/payment_feature_list.h
@@ -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.
+
+#ifndef COMPONENTS_PAYMENTS_CONTENT_ANDROID_PAYMENT_FEATURE_LIST_H_
+#define COMPONENTS_PAYMENTS_CONTENT_ANDROID_PAYMENT_FEATURE_LIST_H_
+
+#include <base/feature_list.h>
+#include <jni.h>
+
+namespace payments {
+namespace android {
+
+// Android only payment features in alphabetical order:
+extern const base::Feature kAndroidAppPaymentUpdateEvents;
+extern const base::Feature kScrollToExpandPaymentHandler;
+
+} // namespace android
+} // namespace payments
+
+#endif // COMPONENTS_PAYMENTS_CONTENT_ANDROID_PAYMENT_FEATURE_LIST_H_
diff --git a/chromium/components/payments/content/android/payment_handler_host.cc b/chromium/components/payments/content/android/payment_handler_host.cc
index 2f2f552088f..0a5cabcdb0d 100644
--- a/chromium/components/payments/content/android/payment_handler_host.cc
+++ b/chromium/components/payments/content/android/payment_handler_host.cc
@@ -19,18 +19,28 @@ namespace android {
jlong JNI_PaymentHandlerHost_Init(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& web_contents,
- const base::android::JavaParamRef<jobject>& delegate) {
+ const base::android::JavaParamRef<jobject>& listener) {
return reinterpret_cast<intptr_t>(
- new PaymentHandlerHost(web_contents, delegate));
+ new PaymentHandlerHost(web_contents, listener));
+}
+
+// static
+base::WeakPtr<payments::PaymentHandlerHost>
+PaymentHandlerHost::FromJavaPaymentHandlerHost(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& payment_handler_host) {
+ return reinterpret_cast<PaymentHandlerHost*>(
+ Java_PaymentHandlerHost_getNativeBridge(env, payment_handler_host))
+ ->payment_handler_host_.AsWeakPtr();
}
PaymentHandlerHost::PaymentHandlerHost(
const base::android::JavaParamRef<jobject>& web_contents,
- const base::android::JavaParamRef<jobject>& delegate)
- : delegate_(delegate),
+ const base::android::JavaParamRef<jobject>& listener)
+ : listener_(listener),
payment_handler_host_(
content::WebContents::FromJavaWebContents(web_contents),
- /*delegate=*/this) {}
+ /*delegate=*/&listener_) {}
PaymentHandlerHost::~PaymentHandlerHost() {}
@@ -39,10 +49,6 @@ jboolean PaymentHandlerHost::IsWaitingForPaymentDetailsUpdate(
return payment_handler_host_.is_waiting_for_payment_details_update();
}
-jlong PaymentHandlerHost::GetNativePaymentHandlerHost(JNIEnv* env) {
- return reinterpret_cast<intptr_t>(&payment_handler_host_);
-}
-
void PaymentHandlerHost::Destroy(JNIEnv* env) {
delete this;
}
@@ -62,49 +68,5 @@ void PaymentHandlerHost::OnPaymentDetailsNotUpdated(JNIEnv* env) {
payment_handler_host_.OnPaymentDetailsNotUpdated();
}
-bool PaymentHandlerHost::ChangePaymentMethod(
- const std::string& method_name,
- const std::string& stringified_data) {
- JNIEnv* env = base::android::AttachCurrentThread();
- return Java_PaymentHandlerHostDelegate_changePaymentMethodFromPaymentHandler(
- env, delegate_, base::android::ConvertUTF8ToJavaString(env, method_name),
- base::android::ConvertUTF8ToJavaString(env, stringified_data));
-}
-
-bool PaymentHandlerHost::ChangeShippingOption(
- const std::string& shipping_option_id) {
- JNIEnv* env = base::android::AttachCurrentThread();
- return Java_PaymentHandlerHostDelegate_changeShippingOptionFromPaymentHandler(
- env, delegate_,
- base::android::ConvertUTF8ToJavaString(env, shipping_option_id));
-}
-
-bool PaymentHandlerHost::ChangeShippingAddress(
- mojom::PaymentAddressPtr shipping_address) {
- JNIEnv* env = base::android::AttachCurrentThread();
- base::android::ScopedJavaLocalRef<jobject> jshipping_address =
- Java_PaymentHandlerHost_createShippingAddress(
- env,
- base::android::ConvertUTF8ToJavaString(env,
- shipping_address->country),
- base::android::ToJavaArrayOfStrings(env,
- shipping_address->address_line),
- base::android::ConvertUTF8ToJavaString(env, shipping_address->region),
- base::android::ConvertUTF8ToJavaString(env, shipping_address->city),
- base::android::ConvertUTF8ToJavaString(
- env, shipping_address->dependent_locality),
- base::android::ConvertUTF8ToJavaString(env,
- shipping_address->postal_code),
- base::android::ConvertUTF8ToJavaString(
- env, shipping_address->sorting_code),
- base::android::ConvertUTF8ToJavaString(
- env, shipping_address->organization),
- base::android::ConvertUTF8ToJavaString(env,
- shipping_address->recipient),
- base::android::ConvertUTF8ToJavaString(env, shipping_address->phone));
- return Java_PaymentHandlerHostDelegate_changeShippingAddressFromPaymentHandler(
- env, delegate_, jshipping_address);
-}
-
} // namespace android
} // namespace payments
diff --git a/chromium/components/payments/content/android/payment_handler_host.h b/chromium/components/payments/content/android/payment_handler_host.h
index ef520967a6d..186a2591d7e 100644
--- a/chromium/components/payments/content/android/payment_handler_host.h
+++ b/chromium/components/payments/content/android/payment_handler_host.h
@@ -9,6 +9,8 @@
#include "base/android/scoped_java_ref.h"
#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "components/payments/content/android/payment_request_update_event_listener.h"
#include "components/payments/content/payment_handler_host.h"
namespace payments {
@@ -17,39 +19,48 @@ namespace android {
// The native bridge for Java to interact with the payment handler host.
// Object relationship diagram:
//
-// PaymentRequestImpl.java ---- implements ----> PaymentHandlerHostDelegate
+// PaymentRequestImpl.java --- implements ---> PaymentRequestUpdateEventListener
// | ^
-// owns |_________
-// | |
-// v |
-// PaymentHandlerHost.java |
-// | |
-// owns |
-// | delegate
-// v |
-// android/payment_handler_host.h -- implements -> PaymentHandlerHost::Delegate
-// | ^
-// owns |
+// owns |________________________
+// | |
+// v |
+// PaymentHandlerHost.java |
+// | |
+// owns |
+// | listener
+// v |
+// android/payment_handler_host.h |
+// | | |
+// owns | |
+// | owns |
+// | | |
+// | v |
+// | android/payment_request_update_event_listener.h
+// | ^ \ ---- implements ---> PaymentHandlerHost::Delegate
+// | |
// | delegate
// v |
// payment_handler_host.h
-class PaymentHandlerHost : public payments::PaymentHandlerHost::Delegate {
+class PaymentHandlerHost {
public:
- // The |delegate| must implement PaymentHandlerHostDelegate from
- // PaymentHandlerHost.java. The |web_contents| should be from the same browser
- // context as the payment handler and are used for logging in developr tools.
+ // Converts a Java PaymentHandlerHost object into a C++ cross-platform
+ // payments::PaymentHandlerHost object. The returned object is ultimately
+ // owned by the Java PaymentHandlerHost.
+ static base::WeakPtr<payments::PaymentHandlerHost> FromJavaPaymentHandlerHost(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& payment_handler_host);
+
+ // The |listener| must implement PaymentRequestUpdateEventListener. The
+ // |web_contents| should be from the same browser context as the payment
+ // handler and are used for logging in developr tools.
PaymentHandlerHost(const base::android::JavaParamRef<jobject>& web_contents,
- const base::android::JavaParamRef<jobject>& delegate);
- ~PaymentHandlerHost() override;
+ const base::android::JavaParamRef<jobject>& listener);
+ ~PaymentHandlerHost();
// Checks whether any payment method, shipping address or shipping option
// change is currently in progress.
jboolean IsWaitingForPaymentDetailsUpdate(JNIEnv* env) const;
- // Returns the pointer to the payments::PaymentHandlerHost for binding to its
- // IPC endpoint in service_worker_payment_app_bridge.cc.
- jlong GetNativePaymentHandlerHost(JNIEnv* env);
-
// Destroys this object.
void Destroy(JNIEnv* env);
@@ -64,14 +75,7 @@ class PaymentHandlerHost : public payments::PaymentHandlerHost::Delegate {
void OnPaymentDetailsNotUpdated(JNIEnv* env);
private:
- // PaymentHandlerHost::Delegate implementation:
- bool ChangePaymentMethod(const std::string& method_name,
- const std::string& stringified_data) override;
- bool ChangeShippingOption(const std::string& shipping_option_id) override;
- bool ChangeShippingAddress(
- mojom::PaymentAddressPtr shipping_address) override;
-
- base::android::ScopedJavaGlobalRef<jobject> delegate_;
+ PaymentRequestUpdateEventListener listener_;
payments::PaymentHandlerHost payment_handler_host_;
DISALLOW_COPY_AND_ASSIGN(PaymentHandlerHost);
diff --git a/chromium/components/payments/content/android/payment_request_spec.cc b/chromium/components/payments/content/android/payment_request_spec.cc
new file mode 100644
index 00000000000..8c26aceeef8
--- /dev/null
+++ b/chromium/components/payments/content/android/payment_request_spec.cc
@@ -0,0 +1,90 @@
+// 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_request_spec.h"
+
+#include <utility>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_array.h"
+#include "base/android/jni_string.h"
+#include "components/payments/content/android/byte_buffer_helper.h"
+#include "components/payments/content/android/jni_headers/PaymentRequestSpec_jni.h"
+#include "third_party/blink/public/mojom/payments/payment_request.mojom.h"
+
+namespace payments {
+namespace android {
+
+// static
+jlong JNI_PaymentRequestSpec_Create(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& joptions_byte_buffer,
+ const base::android::JavaParamRef<jobject>& jdetails_byte_buffer,
+ const base::android::JavaParamRef<jobjectArray>& jmethod_data_byte_buffers,
+ const base::android::JavaParamRef<jstring>& japp_locale) {
+ mojom::PaymentOptionsPtr options;
+ bool success =
+ DeserializeFromJavaByteBuffer(env, joptions_byte_buffer, &options);
+ DCHECK(success);
+
+ mojom::PaymentDetailsPtr details;
+ success = DeserializeFromJavaByteBuffer(env, jdetails_byte_buffer, &details);
+ DCHECK(success);
+
+ std::vector<mojom::PaymentMethodDataPtr> method_data;
+ success = DeserializeFromJavaByteBufferArray(env, jmethod_data_byte_buffers,
+ &method_data);
+ DCHECK(success);
+
+ return reinterpret_cast<intptr_t>(
+ new PaymentRequestSpec(std::make_unique<payments::PaymentRequestSpec>(
+ std::move(options), std::move(details), std::move(method_data),
+ /*delegate=*/nullptr,
+ base::android::ConvertJavaStringToUTF8(env, japp_locale))));
+}
+
+// static
+payments::PaymentRequestSpec* PaymentRequestSpec::FromJavaPaymentRequestSpec(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& jpayment_request_spec) {
+ return reinterpret_cast<PaymentRequestSpec*>(
+ Java_PaymentRequestSpec_getNativePointer(env,
+ jpayment_request_spec))
+ ->spec_.get();
+}
+
+PaymentRequestSpec::PaymentRequestSpec(
+ std::unique_ptr<payments::PaymentRequestSpec> spec)
+ : spec_(std::move(spec)) {}
+
+void PaymentRequestSpec::UpdateWith(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& jdetails_byte_buffer) {
+ mojom::PaymentDetailsPtr details;
+ bool success =
+ DeserializeFromJavaByteBuffer(env, jdetails_byte_buffer, &details);
+ DCHECK(success);
+
+ spec_->UpdateWith(std::move(details));
+}
+
+void PaymentRequestSpec::Retry(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& jvalidation_errors_buffer) {
+ mojom::PaymentValidationErrorsPtr validation_errors;
+ bool success = DeserializeFromJavaByteBuffer(env, jvalidation_errors_buffer,
+ &validation_errors);
+ DCHECK(success);
+
+ spec_->Retry(std::move(validation_errors));
+}
+
+void PaymentRequestSpec::Destroy(JNIEnv* env) {
+ delete this;
+}
+
+PaymentRequestSpec::~PaymentRequestSpec() = default;
+
+} // namespace android
+} // namespace payments
diff --git a/chromium/components/payments/content/android/payment_request_spec.h b/chromium/components/payments/content/android/payment_request_spec.h
new file mode 100644
index 00000000000..ee3ea297733
--- /dev/null
+++ b/chromium/components/payments/content/android/payment_request_spec.h
@@ -0,0 +1,66 @@
+// 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_REQUEST_SPEC_H_
+#define COMPONENTS_PAYMENTS_CONTENT_ANDROID_PAYMENT_REQUEST_SPEC_H_
+
+#include <jni.h>
+#include <memory>
+
+#include "base/android/scoped_java_ref.h"
+#include "components/payments/content/payment_request_spec.h"
+
+namespace payments {
+namespace android {
+
+// A bridge for Android to own a C++ PaymentRequestSpec object.
+//
+// Object ownership diagram:
+//
+// PaymentRequestImpl.java
+// |
+// v
+// PaymentRequestSpec.java
+// |
+// v
+// android/payment_request_spec.h
+// |
+// v
+// payment_request_spec.h
+class PaymentRequestSpec {
+ public:
+ // Returns the C++ PaymentRequestSpec that is owned by the Java
+ // PaymentRequestSpec, or nullptr after the Java method
+ // PaymentRequestSpec.destroy() has been called.
+ static payments::PaymentRequestSpec* FromJavaPaymentRequestSpec(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& jpayment_request_spec);
+
+ // Constructs the Android bridge with the given |spec|.
+ explicit PaymentRequestSpec(
+ std::unique_ptr<payments::PaymentRequestSpec> spec);
+
+ // Called when the renderer updates the payment details in response to, e.g.,
+ // new shipping address.
+ void UpdateWith(JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& jdetails_buffer);
+
+ // Called when the merchant retries a failed payment.
+ void Retry(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& jvalidation_errors_buffer);
+
+ // Destroys this bridge.
+ void Destroy(JNIEnv* env);
+
+ private:
+ ~PaymentRequestSpec();
+
+ std::unique_ptr<payments::PaymentRequestSpec> spec_;
+};
+
+} // namespace android
+} // namespace payments
+
+#endif // COMPONENTS_PAYMENTS_CONTENT_ANDROID_PAYMENT_REQUEST_SPEC_H_
diff --git a/chromium/components/payments/content/android/payment_request_update_event_listener.cc b/chromium/components/payments/content/android/payment_request_update_event_listener.cc
new file mode 100644
index 00000000000..195c29c6ff1
--- /dev/null
+++ b/chromium/components/payments/content/android/payment_request_update_event_listener.cc
@@ -0,0 +1,50 @@
+// 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_request_update_event_listener.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "components/payments/content/android/jni_headers/PaymentRequestUpdateEventListener_jni.h"
+
+namespace payments {
+namespace android {
+
+PaymentRequestUpdateEventListener::PaymentRequestUpdateEventListener(
+ const base::android::JavaParamRef<jobject>& listener)
+ : listener_(listener) {}
+
+PaymentRequestUpdateEventListener::~PaymentRequestUpdateEventListener() {}
+
+bool PaymentRequestUpdateEventListener::ChangePaymentMethod(
+ const std::string& method_name,
+ const std::string& stringified_data) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ return Java_PaymentRequestUpdateEventListener_changePaymentMethodFromInvokedApp(
+ env, listener_, base::android::ConvertUTF8ToJavaString(env, method_name),
+ base::android::ConvertUTF8ToJavaString(env, stringified_data));
+}
+
+bool PaymentRequestUpdateEventListener::ChangeShippingOption(
+ const std::string& shipping_option_id) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ return Java_PaymentRequestUpdateEventListener_changeShippingOptionFromInvokedApp(
+ env, listener_,
+ base::android::ConvertUTF8ToJavaString(env, shipping_option_id));
+}
+
+bool PaymentRequestUpdateEventListener::ChangeShippingAddress(
+ mojom::PaymentAddressPtr shipping_address) {
+ std::vector<uint8_t> byte_vector =
+ mojom::PaymentAddress::Serialize(&shipping_address);
+ JNIEnv* env = base::android::AttachCurrentThread();
+ return Java_PaymentRequestUpdateEventListener_changeShippingAddress(
+ env, listener_,
+ base::android::ScopedJavaLocalRef<jobject>(
+ env,
+ env->NewDirectByteBuffer(byte_vector.data(), byte_vector.size())));
+}
+
+} // namespace android
+} // namespace payments
diff --git a/chromium/components/payments/content/android/payment_request_update_event_listener.h b/chromium/components/payments/content/android/payment_request_update_event_listener.h
new file mode 100644
index 00000000000..63708b059f6
--- /dev/null
+++ b/chromium/components/payments/content/android/payment_request_update_event_listener.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_REQUEST_UPDATE_EVENT_LISTENER_H_
+#define COMPONENTS_PAYMENTS_CONTENT_ANDROID_PAYMENT_REQUEST_UPDATE_EVENT_LISTENER_H_
+
+#include <jni.h>
+
+#include "base/android/scoped_java_ref.h"
+#include "components/payments/content/payment_handler_host.h"
+
+namespace payments {
+namespace android {
+
+class PaymentRequestUpdateEventListener
+ : public payments::PaymentHandlerHost::Delegate {
+ public:
+ explicit PaymentRequestUpdateEventListener(
+ const base::android::JavaParamRef<jobject>& listener);
+ ~PaymentRequestUpdateEventListener() override;
+
+ // PaymentHandlerHost::Delegate implementation:
+ bool ChangePaymentMethod(const std::string& method_name,
+ const std::string& stringified_data) override;
+ bool ChangeShippingOption(const std::string& shipping_option_id) override;
+ bool ChangeShippingAddress(
+ mojom::PaymentAddressPtr shipping_address) override;
+
+ private:
+ base::android::ScopedJavaGlobalRef<jobject> listener_;
+};
+
+} // namespace android
+} // namespace payments
+
+#endif // COMPONENTS_PAYMENTS_CONTENT_ANDROID_PAYMENT_REQUEST_UPDATE_EVENT_LISTENER_H_
diff --git a/chromium/components/payments/content/autofill_payment_app.cc b/chromium/components/payments/content/autofill_payment_app.cc
new file mode 100644
index 00000000000..b57189934f6
--- /dev/null
+++ b/chromium/components/payments/content/autofill_payment_app.cc
@@ -0,0 +1,244 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/payments/content/autofill_payment_app.h"
+
+#include <algorithm>
+#include <memory>
+
+#include "base/bind.h"
+#include "base/json/json_writer.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/stl_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "components/autofill/core/browser/autofill_data_util.h"
+#include "components/autofill/core/browser/autofill_type.h"
+#include "components/autofill/core/browser/field_types.h"
+#include "components/autofill/core/browser/geo/autofill_country.h"
+#include "components/autofill/core/browser/personal_data_manager.h"
+#include "components/autofill/core/browser/validation.h"
+#include "components/autofill/core/common/autofill_clock.h"
+#include "components/payments/core/autofill_card_validation.h"
+#include "components/payments/core/basic_card_response.h"
+#include "components/payments/core/features.h"
+#include "components/payments/core/method_strings.h"
+#include "components/payments/core/payment_request_base_delegate.h"
+#include "components/payments/core/payment_request_data_util.h"
+#include "components/payments/core/payments_experimental_features.h"
+
+namespace payments {
+
+AutofillPaymentApp::AutofillPaymentApp(
+ const std::string& method_name,
+ const autofill::CreditCard& card,
+ const std::vector<autofill::AutofillProfile*>& billing_profiles,
+ const std::string& app_locale,
+ PaymentRequestBaseDelegate* payment_request_delegate)
+ : PaymentApp(autofill::data_util::GetPaymentRequestData(card.network())
+ .icon_resource_id,
+ PaymentApp::Type::AUTOFILL),
+ method_name_(method_name),
+ credit_card_(card),
+ billing_profiles_(billing_profiles),
+ app_locale_(app_locale),
+ delegate_(nullptr),
+ payment_request_delegate_(payment_request_delegate) {
+ app_method_names_.insert(methods::kBasicCard);
+}
+
+AutofillPaymentApp::~AutofillPaymentApp() {}
+
+void AutofillPaymentApp::InvokePaymentApp(PaymentApp::Delegate* delegate) {
+ DCHECK(delegate);
+ // There can be only one FullCardRequest going on at a time. If |delegate_| is
+ // not null, there's already an active request, which shouldn't happen.
+ // |delegate_| is reset to nullptr when the request succeeds or fails.
+ DCHECK(!delegate_);
+ delegate_ = delegate;
+
+ // Get the billing address.
+ if (!credit_card_.billing_address_id().empty()) {
+ autofill::AutofillProfile* billing_address =
+ autofill::PersonalDataManager::GetProfileFromProfilesByGUID(
+ credit_card_.billing_address_id(), billing_profiles_);
+ if (billing_address)
+ billing_address_ = *billing_address;
+ }
+
+ is_waiting_for_billing_address_normalization_ = true;
+ is_waiting_for_card_unmask_ = true;
+
+ // Start the normalization of the billing address.
+ payment_request_delegate_->GetAddressNormalizer()->NormalizeAddressAsync(
+ billing_address_, /*timeout_seconds=*/5,
+ base::BindOnce(&AutofillPaymentApp::OnAddressNormalized,
+ weak_ptr_factory_.GetWeakPtr()));
+
+ payment_request_delegate_->DoFullCardRequest(credit_card_,
+ weak_ptr_factory_.GetWeakPtr());
+}
+
+bool AutofillPaymentApp::IsCompleteForPayment() const {
+ // COMPLETE or EXPIRED cards are considered valid for payment. The user will
+ // be prompted to enter the new expiration at the CVC step.
+ return GetCompletionStatusForCard(credit_card_, app_locale_,
+ billing_profiles_) <= CREDIT_CARD_EXPIRED;
+}
+
+uint32_t AutofillPaymentApp::GetCompletenessScore() const {
+ return ::payments::GetCompletenessScore(credit_card_, app_locale_,
+ billing_profiles_);
+}
+
+bool AutofillPaymentApp::CanPreselect() const {
+ return IsCompleteForPayment();
+}
+
+base::string16 AutofillPaymentApp::GetMissingInfoLabel() const {
+ return GetCompletionMessageForCard(
+ GetCompletionStatusForCard(credit_card_, app_locale_, billing_profiles_));
+}
+
+bool AutofillPaymentApp::HasEnrolledInstrument() const {
+ CreditCardCompletionStatus status =
+ GetCompletionStatusForCard(credit_card_, app_locale_, billing_profiles_);
+ if (PaymentsExperimentalFeatures::IsEnabled(
+ features::kStrictHasEnrolledAutofillInstrument)) {
+ return status == CREDIT_CARD_COMPLETE &&
+ is_requested_autofill_data_available_;
+ }
+
+ // Card has to have a cardholder name and number for the purposes of
+ // CanMakePayment. An expired card is still valid at this stage.
+ return !(status & CREDIT_CARD_NO_CARDHOLDER ||
+ status & CREDIT_CARD_NO_NUMBER);
+}
+
+void AutofillPaymentApp::RecordUse() {
+ // Record the use of the credit card.
+ payment_request_delegate_->GetPersonalDataManager()->RecordUseOf(
+ credit_card_);
+}
+
+bool AutofillPaymentApp::NeedsInstallation() const {
+ // Autofill payment app is built-in, so it doesn't need installation.
+ return false;
+}
+
+std::string AutofillPaymentApp::GetId() const {
+ return credit_card_.guid();
+}
+
+base::string16 AutofillPaymentApp::GetLabel() const {
+ return credit_card_.NetworkAndLastFourDigits();
+}
+
+base::string16 AutofillPaymentApp::GetSublabel() const {
+ return credit_card_.GetInfo(
+ autofill::AutofillType(autofill::CREDIT_CARD_NAME_FULL), app_locale_);
+}
+
+bool AutofillPaymentApp::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);
+ if (!is_valid)
+ return false;
+
+ if (supported_networks_specified) {
+ std::string basic_card_network =
+ autofill::data_util::GetPaymentRequestData(credit_card_.network())
+ .basic_card_issuer_network;
+ if (supported_networks.find(basic_card_network) == supported_networks.end())
+ return false;
+ }
+
+ return true;
+}
+
+base::WeakPtr<PaymentApp> AutofillPaymentApp::AsWeakPtr() {
+ return weak_ptr_factory_.GetWeakPtr();
+}
+
+bool AutofillPaymentApp::HandlesShippingAddress() const {
+ return false;
+}
+
+bool AutofillPaymentApp::HandlesPayerName() const {
+ return false;
+}
+
+bool AutofillPaymentApp::HandlesPayerEmail() const {
+ return false;
+}
+
+bool AutofillPaymentApp::HandlesPayerPhone() const {
+ return false;
+}
+
+void AutofillPaymentApp::OnFullCardRequestSucceeded(
+ const autofill::payments::FullCardRequest& /* full_card_request */,
+ const autofill::CreditCard& card,
+ const base::string16& cvc) {
+ DCHECK(delegate_);
+ credit_card_ = card;
+ cvc_ = cvc;
+ is_waiting_for_card_unmask_ = false;
+
+ if (!is_waiting_for_billing_address_normalization_)
+ GenerateBasicCardResponse();
+}
+
+void AutofillPaymentApp::OnFullCardRequestFailed() {
+ // The user may have cancelled the unmask or something has gone wrong (e.g.,
+ // the network request failed). In all cases, reset the |delegate_| so another
+ // request can start.
+ delegate_ = nullptr;
+}
+
+void AutofillPaymentApp::RecordMissingFieldsForApp() const {
+ CreditCardCompletionStatus completion_status =
+ GetCompletionStatusForCard(credit_card_, app_locale_, billing_profiles_);
+ if (completion_status == CREDIT_CARD_COMPLETE)
+ return;
+
+ // Record the missing fields from card completion status.
+ base::UmaHistogramSparse("PaymentRequest.MissingPaymentFields",
+ completion_status);
+}
+
+void AutofillPaymentApp::GenerateBasicCardResponse() {
+ DCHECK(!is_waiting_for_billing_address_normalization_);
+ DCHECK(!is_waiting_for_card_unmask_);
+ DCHECK(delegate_);
+
+ std::unique_ptr<base::DictionaryValue> response_value =
+ payments::data_util::GetBasicCardResponseFromAutofillCreditCard(
+ credit_card_, cvc_, billing_address_, app_locale_)
+ ->ToDictionaryValue();
+ std::string stringified_details;
+ base::JSONWriter::Write(*response_value, &stringified_details);
+ delegate_->OnInstrumentDetailsReady(method_name_, stringified_details,
+ PayerData());
+
+ delegate_ = nullptr;
+ cvc_ = base::UTF8ToUTF16("");
+}
+
+void AutofillPaymentApp::OnAddressNormalized(
+ bool success,
+ const autofill::AutofillProfile& normalized_profile) {
+ DCHECK(is_waiting_for_billing_address_normalization_);
+
+ billing_address_ = normalized_profile;
+ is_waiting_for_billing_address_normalization_ = false;
+
+ if (!is_waiting_for_card_unmask_)
+ GenerateBasicCardResponse();
+}
+
+} // namespace payments
diff --git a/chromium/components/payments/content/autofill_payment_app.h b/chromium/components/payments/content/autofill_payment_app.h
new file mode 100644
index 00000000000..2f6b4df8b87
--- /dev/null
+++ b/chromium/components/payments/content/autofill_payment_app.h
@@ -0,0 +1,120 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PAYMENTS_CONTENT_AUTOFILL_PAYMENT_APP_H_
+#define COMPONENTS_PAYMENTS_CONTENT_AUTOFILL_PAYMENT_APP_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/strings/string16.h"
+#include "build/build_config.h"
+#include "components/autofill/core/browser/address_normalizer.h"
+#include "components/autofill/core/browser/data_model/autofill_profile.h"
+#include "components/autofill/core/browser/data_model/credit_card.h"
+#include "components/autofill/core/browser/payments/full_card_request.h"
+#include "components/payments/content/payment_app.h"
+
+namespace payments {
+
+class PaymentRequestBaseDelegate;
+
+// Represents an autofill credit card in Payment Request.
+class AutofillPaymentApp
+ : public PaymentApp,
+ public autofill::payments::FullCardRequest::ResultDelegate {
+ public:
+ // |billing_profiles| is owned by the caller and should outlive this object.
+ // |payment_request_delegate| must outlive this object.
+ AutofillPaymentApp(
+ const std::string& method_name,
+ const autofill::CreditCard& card,
+ const std::vector<autofill::AutofillProfile*>& billing_profiles,
+ const std::string& app_locale,
+ PaymentRequestBaseDelegate* payment_request_delegate);
+ ~AutofillPaymentApp() override;
+
+ // PaymentApp:
+ void InvokePaymentApp(PaymentApp::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;
+ 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;
+
+ // autofill::payments::FullCardRequest::ResultDelegate:
+ void OnFullCardRequestSucceeded(
+ const autofill::payments::FullCardRequest& full_card_request,
+ const autofill::CreditCard& card,
+ const base::string16& cvc) override;
+ void OnFullCardRequestFailed() override;
+
+ void RecordMissingFieldsForApp() const;
+
+ // Sets whether the complete and valid autofill data for merchant's request is
+ // available.
+ void set_is_requested_autofill_data_available(bool available) {
+ is_requested_autofill_data_available_ = available;
+ }
+ autofill::CreditCard* credit_card() { return &credit_card_; }
+ const autofill::CreditCard* credit_card() const { return &credit_card_; }
+
+ const std::string& method_name() const { return method_name_; }
+
+ private:
+ // Generates the basic card response and sends it to the delegate.
+ void GenerateBasicCardResponse();
+
+ // To be used as AddressNormalizer::NormalizationCallback.
+ void OnAddressNormalized(bool success,
+ const autofill::AutofillProfile& normalized_profile);
+
+ const std::string method_name_;
+
+ // A copy of the card is owned by this object.
+ autofill::CreditCard credit_card_;
+
+ // Not owned by this object, should outlive this.
+ const std::vector<autofill::AutofillProfile*>& billing_profiles_;
+
+ const std::string app_locale_;
+
+ PaymentApp::Delegate* delegate_;
+ PaymentRequestBaseDelegate* payment_request_delegate_;
+ autofill::AutofillProfile billing_address_;
+
+ base::string16 cvc_;
+
+ bool is_waiting_for_card_unmask_;
+ bool is_waiting_for_billing_address_normalization_;
+
+ // True when complete and valid autofill data for merchant's request is
+ // available, e.g., if merchant specifies `requestPayerEmail: true`, then this
+ // variable is true only if the autofill data contains a valid email address.
+ bool is_requested_autofill_data_available_ = false;
+
+ base::WeakPtrFactory<AutofillPaymentApp> weak_ptr_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(AutofillPaymentApp);
+};
+
+} // namespace payments
+
+#endif // COMPONENTS_PAYMENTS_CONTENT_AUTOFILL_PAYMENT_APP_H_
diff --git a/chromium/components/payments/content/autofill_payment_app_factory.cc b/chromium/components/payments/content/autofill_payment_app_factory.cc
index 5e39b1598f0..b6eadc5e02c 100644
--- a/chromium/components/payments/content/autofill_payment_app_factory.cc
+++ b/chromium/components/payments/content/autofill_payment_app_factory.cc
@@ -9,9 +9,9 @@
#include "base/feature_list.h"
#include "components/autofill/core/browser/autofill_data_util.h"
#include "components/autofill/core/browser/personal_data_manager.h"
+#include "components/payments/content/autofill_payment_app.h"
#include "components/payments/content/content_payment_request_delegate.h"
#include "components/payments/content/payment_request_spec.h"
-#include "components/payments/core/autofill_payment_app.h"
#include "components/payments/core/features.h"
namespace payments {
diff --git a/chromium/components/payments/content/autofill_payment_app_unittest.cc b/chromium/components/payments/content/autofill_payment_app_unittest.cc
new file mode 100644
index 00000000000..acaf906122c
--- /dev/null
+++ b/chromium/components/payments/content/autofill_payment_app_unittest.cc
@@ -0,0 +1,362 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/payments/content/autofill_payment_app.h"
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/autofill/core/browser/address_normalizer.h"
+#include "components/autofill/core/browser/autofill_test_utils.h"
+#include "components/autofill/core/browser/data_model/autofill_profile.h"
+#include "components/autofill/core/browser/data_model/credit_card.h"
+#include "components/autofill/core/browser/payments/full_card_request.h"
+#include "components/autofill/core/browser/payments/payments_client.h"
+#include "components/autofill/core/browser/personal_data_manager.h"
+#include "components/autofill/core/browser/test_address_normalizer.h"
+#include "components/autofill/core/browser/test_autofill_client.h"
+#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"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace payments {
+
+namespace {
+
+class FakePaymentAppDelegate : public PaymentApp::Delegate {
+ public:
+ FakePaymentAppDelegate() {}
+
+ void OnInstrumentDetailsReady(const std::string& method_name,
+ const std::string& stringified_details,
+ const PayerData& payer_data) override {
+ on_instrument_details_ready_called_ = true;
+ }
+
+ void OnInstrumentDetailsError(const std::string& error_message) override {
+ on_instrument_details_error_called_ = true;
+ }
+
+ bool WasOnInstrumentDetailsReadyCalled() {
+ return on_instrument_details_ready_called_;
+ }
+
+ bool WasOnInstrumentDetailsErrorCalled() {
+ return on_instrument_details_error_called_;
+ }
+
+ private:
+ bool on_instrument_details_ready_called_ = false;
+ bool on_instrument_details_error_called_ = false;
+};
+
+class FakePaymentRequestDelegate : public PaymentRequestDelegate {
+ public:
+ FakePaymentRequestDelegate()
+ : locale_("en-US"),
+ last_committed_url_("https://shop.com"),
+ personal_data_("en-US"),
+ test_shared_loader_factory_(
+ base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+ &test_url_loader_factory_)),
+ payments_client_(test_shared_loader_factory_,
+ /*identity_manager=*/nullptr,
+ /*account_info_getter=*/nullptr),
+ full_card_request_(&autofill_client_,
+ &payments_client_,
+ &personal_data_) {}
+ void ShowDialog(PaymentRequest* request) override {}
+
+ void CloseDialog() override {}
+
+ void ShowErrorMessage() override {}
+
+ autofill::PersonalDataManager* GetPersonalDataManager() override {
+ return nullptr;
+ }
+
+ const std::string& GetApplicationLocale() const override { return locale_; }
+
+ bool IsOffTheRecord() const override { return false; }
+
+ const GURL& GetLastCommittedURL() const override {
+ return last_committed_url_;
+ }
+
+ void DoFullCardRequest(
+ const autofill::CreditCard& credit_card,
+ base::WeakPtr<autofill::payments::FullCardRequest::ResultDelegate>
+ result_delegate) override {
+ full_card_request_card_ = credit_card;
+ full_card_result_delegate_ = result_delegate;
+ }
+
+ autofill::AddressNormalizer* GetAddressNormalizer() override {
+ return &address_normalizer_;
+ }
+
+ void CompleteFullCardRequest() {
+ full_card_result_delegate_->OnFullCardRequestSucceeded(
+ full_card_request_, full_card_request_card_, base::ASCIIToUTF16("123"));
+ }
+
+ autofill::RegionDataLoader* GetRegionDataLoader() override { return nullptr; }
+
+ ukm::UkmRecorder* GetUkmRecorder() override { return nullptr; }
+
+ private:
+ std::string locale_;
+ const GURL last_committed_url_;
+ autofill::TestAddressNormalizer address_normalizer_;
+ autofill::PersonalDataManager personal_data_;
+ network::TestURLLoaderFactory test_url_loader_factory_;
+ scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_;
+ autofill::TestAutofillClient autofill_client_;
+ autofill::payments::PaymentsClient payments_client_;
+ autofill::payments::FullCardRequest full_card_request_;
+ autofill::CreditCard full_card_request_card_;
+ base::WeakPtr<autofill::payments::FullCardRequest::ResultDelegate>
+ full_card_result_delegate_;
+ DISALLOW_COPY_AND_ASSIGN(FakePaymentRequestDelegate);
+};
+
+} // namespace
+
+class AutofillPaymentAppTest : public testing::Test {
+ protected:
+ AutofillPaymentAppTest()
+ : address_(autofill::test::GetFullProfile()),
+ local_card_(autofill::test::GetCreditCard()),
+ billing_profiles_({&address_}) {
+ local_card_.set_billing_address_id(address_.guid());
+ }
+
+ autofill::CreditCard& local_credit_card() { return local_card_; }
+ std::vector<autofill::AutofillProfile*>& billing_profiles() {
+ return billing_profiles_;
+ }
+
+ private:
+ autofill::AutofillProfile address_;
+ autofill::CreditCard local_card_;
+ std::vector<autofill::AutofillProfile*> billing_profiles_;
+
+ DISALLOW_COPY_AND_ASSIGN(AutofillPaymentAppTest);
+};
+
+// A valid local credit card is a valid app for payment.
+TEST_F(AutofillPaymentAppTest, IsCompleteForPayment) {
+ AutofillPaymentApp app("visa", local_credit_card(), billing_profiles(),
+ "en-US", nullptr);
+ EXPECT_TRUE(app.IsCompleteForPayment());
+ EXPECT_TRUE(app.GetMissingInfoLabel().empty());
+}
+
+// An expired local card is still a valid app for payment.
+TEST_F(AutofillPaymentAppTest, IsCompleteForPayment_Expired) {
+ autofill::CreditCard& card = local_credit_card();
+ card.SetExpirationYear(2016); // Expired.
+ AutofillPaymentApp app("visa", card, billing_profiles(), "en-US", nullptr);
+ EXPECT_TRUE(app.IsCompleteForPayment());
+ EXPECT_EQ(base::string16(), app.GetMissingInfoLabel());
+}
+
+// A local card with no name is not a valid app for payment.
+TEST_F(AutofillPaymentAppTest, IsCompleteForPayment_NoName) {
+ autofill::CreditCard& card = local_credit_card();
+ card.SetInfo(autofill::AutofillType(autofill::CREDIT_CARD_NAME_FULL),
+ base::ASCIIToUTF16(""), "en-US");
+ base::string16 missing_info;
+ AutofillPaymentApp app("visa", card, billing_profiles(), "en-US", nullptr);
+ EXPECT_FALSE(app.IsCompleteForPayment());
+ EXPECT_EQ(l10n_util::GetStringUTF16(IDS_PAYMENTS_NAME_ON_CARD_REQUIRED),
+ app.GetMissingInfoLabel());
+}
+
+// A local card with no name is not a valid app for payment.
+TEST_F(AutofillPaymentAppTest, IsCompleteForPayment_NoNumber) {
+ autofill::CreditCard& card = local_credit_card();
+ card.SetNumber(base::ASCIIToUTF16(""));
+ base::string16 missing_info;
+ AutofillPaymentApp app("visa", card, billing_profiles(), "en-US", nullptr);
+ EXPECT_FALSE(app.IsCompleteForPayment());
+ EXPECT_EQ(l10n_util::GetStringUTF16(
+ IDS_PAYMENTS_CARD_NUMBER_INVALID_VALIDATION_MESSAGE),
+ app.GetMissingInfoLabel());
+}
+
+// A local card with no billing address id is not a valid app for
+// payment.
+TEST_F(AutofillPaymentAppTest, IsCompleteForPayment_NoBillinbAddressId) {
+ autofill::CreditCard& card = local_credit_card();
+ card.set_billing_address_id("");
+ base::string16 missing_info;
+ AutofillPaymentApp app("visa", card, billing_profiles(), "en-US", nullptr);
+ EXPECT_FALSE(app.IsCompleteForPayment());
+ EXPECT_EQ(
+ l10n_util::GetStringUTF16(IDS_PAYMENTS_CARD_BILLING_ADDRESS_REQUIRED),
+ app.GetMissingInfoLabel());
+}
+
+// A local card with an invalid billing address id is not a valid app for
+// payment.
+TEST_F(AutofillPaymentAppTest, IsCompleteForPayment_InvalidBillinbAddressId) {
+ autofill::CreditCard& card = local_credit_card();
+ card.set_billing_address_id("InvalidBillingAddressId");
+ base::string16 missing_info;
+ AutofillPaymentApp app("visa", card, billing_profiles(), "en-US", nullptr);
+ EXPECT_FALSE(app.IsCompleteForPayment());
+ EXPECT_EQ(
+ l10n_util::GetStringUTF16(IDS_PAYMENTS_CARD_BILLING_ADDRESS_REQUIRED),
+ app.GetMissingInfoLabel());
+}
+
+// A local card with an incomplete billing address is not a complete app
+// for payment.
+TEST_F(AutofillPaymentAppTest, IsCompleteForPayment_IncompleteBillinbAddress) {
+ autofill::AutofillProfile incomplete_profile =
+ autofill::test::GetIncompleteProfile2();
+ billing_profiles()[0] = &incomplete_profile;
+ autofill::CreditCard& card = local_credit_card();
+ card.set_billing_address_id(incomplete_profile.guid());
+ base::string16 missing_info;
+ AutofillPaymentApp app("visa", card, billing_profiles(), "en-US", nullptr);
+ EXPECT_FALSE(app.IsCompleteForPayment());
+ EXPECT_EQ(
+ l10n_util::GetStringUTF16(IDS_PAYMENTS_CARD_BILLING_ADDRESS_REQUIRED),
+ app.GetMissingInfoLabel());
+}
+
+// A local card with no name and no number is not a valid app for
+// payment.
+TEST_F(AutofillPaymentAppTest, IsCompleteForPayment_MultipleThingsMissing) {
+ autofill::CreditCard& card = local_credit_card();
+ card.SetNumber(base::ASCIIToUTF16(""));
+ card.SetInfo(autofill::AutofillType(autofill::CREDIT_CARD_NAME_FULL),
+ base::ASCIIToUTF16(""), "en-US");
+ AutofillPaymentApp app("visa", card, billing_profiles(), "en-US", nullptr);
+ EXPECT_FALSE(app.IsCompleteForPayment());
+ EXPECT_EQ(l10n_util::GetStringUTF16(IDS_PAYMENTS_MORE_INFORMATION_REQUIRED),
+ app.GetMissingInfoLabel());
+}
+
+// A Masked (server) card is a valid app for payment.
+TEST_F(AutofillPaymentAppTest, IsCompleteForPayment_MaskedCard) {
+ autofill::CreditCard card = autofill::test::GetMaskedServerCard();
+ ASSERT_GT(billing_profiles().size(), 0UL);
+ card.set_billing_address_id(billing_profiles()[0]->guid());
+ AutofillPaymentApp app("visa", card, billing_profiles(), "en-US", nullptr);
+ EXPECT_TRUE(app.IsCompleteForPayment());
+ EXPECT_TRUE(app.GetMissingInfoLabel().empty());
+}
+
+// An expired masked (server) card is still a valid app for payment.
+TEST_F(AutofillPaymentAppTest, IsCompleteForPayment_ExpiredMaskedCard) {
+ autofill::CreditCard card = autofill::test::GetMaskedServerCard();
+ ASSERT_GT(billing_profiles().size(), 0UL);
+ card.set_billing_address_id(billing_profiles()[0]->guid());
+ card.SetExpirationYear(2016); // Expired.
+ AutofillPaymentApp app("visa", card, billing_profiles(), "en-US", nullptr);
+ EXPECT_TRUE(app.IsCompleteForPayment());
+ EXPECT_EQ(base::string16(), app.GetMissingInfoLabel());
+}
+
+// An expired card is a valid app for canMakePayment.
+TEST_F(AutofillPaymentAppTest, HasEnrolledInstrument_Minimal) {
+ autofill::CreditCard& card = local_credit_card();
+ card.SetExpirationYear(2016); // Expired.
+ AutofillPaymentApp app("visa", card, billing_profiles(), "en-US", nullptr);
+ EXPECT_TRUE(app.HasEnrolledInstrument());
+}
+
+// An expired Masked (server) card is a valid app for canMakePayment.
+TEST_F(AutofillPaymentAppTest, HasEnrolledInstrument_MaskedCard) {
+ autofill::CreditCard card = autofill::test::GetMaskedServerCard();
+ card.SetExpirationYear(2016); // Expired.
+ AutofillPaymentApp app("visa", card, billing_profiles(), "en-US", nullptr);
+ EXPECT_TRUE(app.HasEnrolledInstrument());
+}
+
+// A card with no name is not a valid app for canMakePayment.
+TEST_F(AutofillPaymentAppTest, HasEnrolledInstrument_NoName) {
+ autofill::CreditCard& card = local_credit_card();
+ card.SetInfo(autofill::AutofillType(autofill::CREDIT_CARD_NAME_FULL),
+ base::ASCIIToUTF16(""), "en-US");
+ AutofillPaymentApp app("visa", card, billing_profiles(), "en-US", nullptr);
+ EXPECT_FALSE(app.HasEnrolledInstrument());
+}
+
+// A card with no number is not a valid app for canMakePayment.
+TEST_F(AutofillPaymentAppTest, HasEnrolledInstrument_NoNumber) {
+ autofill::CreditCard& card = local_credit_card();
+ card.SetNumber(base::ASCIIToUTF16(""));
+ AutofillPaymentApp app("visa", card, billing_profiles(), "en-US", nullptr);
+ EXPECT_FALSE(app.HasEnrolledInstrument());
+}
+
+// Tests that the autofill app only calls OnappDetailsReady when
+// the billing address has been normalized and the card has been unmasked.
+TEST_F(AutofillPaymentAppTest, InvokePaymentApp_NormalizationBeforeUnmask) {
+ auto personal_data_manager =
+ std::make_unique<autofill::TestPersonalDataManager>();
+ TestPaymentRequestDelegate delegate(personal_data_manager.get());
+ delegate.DelayFullCardRequestCompletion();
+ delegate.test_address_normalizer()->DelayNormalization();
+
+ autofill::CreditCard& card = local_credit_card();
+ card.SetNumber(base::ASCIIToUTF16(""));
+ AutofillPaymentApp app("visa", card, billing_profiles(), "en-US", &delegate);
+
+ FakePaymentAppDelegate app_delegate;
+
+ app.InvokePaymentApp(&app_delegate);
+ EXPECT_FALSE(app_delegate.WasOnInstrumentDetailsReadyCalled());
+ EXPECT_FALSE(app_delegate.WasOnInstrumentDetailsErrorCalled());
+
+ delegate.test_address_normalizer()->CompleteAddressNormalization();
+ EXPECT_FALSE(app_delegate.WasOnInstrumentDetailsReadyCalled());
+ EXPECT_FALSE(app_delegate.WasOnInstrumentDetailsErrorCalled());
+
+ delegate.CompleteFullCardRequest();
+ EXPECT_TRUE(app_delegate.WasOnInstrumentDetailsReadyCalled());
+ EXPECT_FALSE(app_delegate.WasOnInstrumentDetailsErrorCalled());
+}
+
+// Tests that the autofill app only calls OnappDetailsReady when
+// the billing address has been normalized and the card has been unmasked.
+TEST_F(AutofillPaymentAppTest, InvokePaymentApp_UnmaskBeforeNormalization) {
+ auto personal_data_manager =
+ std::make_unique<autofill::TestPersonalDataManager>();
+ TestPaymentRequestDelegate delegate(personal_data_manager.get());
+ delegate.DelayFullCardRequestCompletion();
+ delegate.test_address_normalizer()->DelayNormalization();
+
+ autofill::CreditCard& card = local_credit_card();
+ card.SetNumber(base::ASCIIToUTF16(""));
+ AutofillPaymentApp app("visa", card, billing_profiles(), "en-US", &delegate);
+
+ FakePaymentAppDelegate app_delegate;
+
+ app.InvokePaymentApp(&app_delegate);
+ EXPECT_FALSE(app_delegate.WasOnInstrumentDetailsReadyCalled());
+ EXPECT_FALSE(app_delegate.WasOnInstrumentDetailsErrorCalled());
+
+ delegate.CompleteFullCardRequest();
+ EXPECT_FALSE(app_delegate.WasOnInstrumentDetailsReadyCalled());
+ EXPECT_FALSE(app_delegate.WasOnInstrumentDetailsErrorCalled());
+
+ delegate.test_address_normalizer()->CompleteAddressNormalization();
+ EXPECT_TRUE(app_delegate.WasOnInstrumentDetailsReadyCalled());
+ EXPECT_FALSE(app_delegate.WasOnInstrumentDetailsErrorCalled());
+}
+
+} // 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 b85ab87f569..c6db3f4215b 100644
--- a/chromium/components/payments/content/content_payment_request_delegate.h
+++ b/chromium/components/payments/content/content_payment_request_delegate.h
@@ -44,10 +44,9 @@ class ContentPaymentRequestDelegate : public PaymentRequestDelegate {
virtual bool IsInteractive() const = 0;
// Returns a developer-facing error message for invalid SSL certificate state
- // or an empty string when the SSL certificate is valid. Only EV_SECURE,
- // SECURE, and SECURE_WITH_POLICY_INSTALLED_CERT are considered valid for web
- // payments, unless --ignore-certificate-errors is specified on the command
- // line.
+ // 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.
//
// The |web_contents| parameter should not be null. A null |web_contents|
// parameter will return an "Invalid certificate" error message.
@@ -56,6 +55,10 @@ class ContentPaymentRequestDelegate : public PaymentRequestDelegate {
// Returns whether the UI should be skipped for a "basic-card" scenario. This
// will only be true in tests.
virtual bool SkipUiForBasicCard() 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;
};
} // namespace payments
diff --git a/chromium/components/payments/content/installable_payment_app_crawler.cc b/chromium/components/payments/content/installable_payment_app_crawler.cc
index 84d57673643..5c9f7a45981 100644
--- a/chromium/components/payments/content/installable_payment_app_crawler.cc
+++ b/chromium/components/payments/content/installable_payment_app_crawler.cc
@@ -7,10 +7,11 @@
#include <utility>
#include "base/bind.h"
+#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
-#include "base/task/post_task.h"
#include "components/payments/content/icon/icon_size.h"
+#include "components/payments/core/features.h"
#include "components/payments/core/native_error_strings.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
@@ -32,6 +33,9 @@
namespace payments {
+RefetchedIcon::RefetchedIcon() = default;
+RefetchedIcon::~RefetchedIcon() = default;
+
// TODO(crbug.com/782270): Use cache to accelerate crawling procedure.
InstallablePaymentAppCrawler::InstallablePaymentAppCrawler(
const url::Origin& merchant_origin,
@@ -62,25 +66,40 @@ InstallablePaymentAppCrawler::~InstallablePaymentAppCrawler() {}
void InstallablePaymentAppCrawler::Start(
const std::vector<mojom::PaymentMethodDataPtr>& requested_method_data,
+ std::set<GURL> method_manifest_urls_for_icon_refetch,
FinishedCrawlingCallback callback,
base::OnceClosure finished_using_resources) {
callback_ = std::move(callback);
finished_using_resources_ = std::move(finished_using_resources);
std::set<GURL> manifests_to_download;
- for (const auto& method_data : requested_method_data) {
- if (!base::IsStringUTF8(method_data->supported_method))
- continue;
- GURL url = GURL(method_data->supported_method);
- if (url.is_valid()) {
- manifests_to_download.insert(url);
+ if (method_manifest_urls_for_icon_refetch.empty()) {
+ // Crawl for JIT installable web apps.
+ crawling_mode_ = CrawlingMode::kJustInTimeInstallation;
+ for (const auto& method_data : requested_method_data) {
+ if (!base::IsStringUTF8(method_data->supported_method))
+ continue;
+ GURL url = GURL(method_data->supported_method);
+ if (url.is_valid()) {
+ manifests_to_download.insert(url);
+ }
+ }
+ } else {
+ // Crawl to refetch missing icons of already installed apps.
+ crawling_mode_ = CrawlingMode::kMissingIconRefetch;
+ UMA_HISTOGRAM_BOOLEAN("PaymentRequest.RefetchIconForInstalledApp", true);
+ method_manifest_urls_for_icon_refetch_ =
+ std::move(method_manifest_urls_for_icon_refetch);
+ for (const auto& method : method_manifest_urls_for_icon_refetch_) {
+ DCHECK(method.is_valid());
+ manifests_to_download.insert(method);
}
}
if (manifests_to_download.empty()) {
// Post the result back asynchronously.
- base::PostTask(
- FROM_HERE, {content::BrowserThread::UI},
+ content::GetUIThreadTaskRunner({})->PostTask(
+ FROM_HERE,
base::BindOnce(
&InstallablePaymentAppCrawler::FinishCrawlingPaymentAppsIfReady,
weak_ptr_factory_.GetWeakPtr()));
@@ -247,7 +266,10 @@ void InstallablePaymentAppCrawler::OnPaymentWebAppInstallationInfo(
if (CompleteAndStorePaymentWebAppInfoIfValid(
method_manifest_url, web_app_manifest_url, std::move(app_info))) {
if (!DownloadAndDecodeWebAppIcon(method_manifest_url, web_app_manifest_url,
- std::move(icons))) {
+ std::move(icons)) &&
+ crawling_mode_ == CrawlingMode::kJustInTimeInstallation &&
+ !base::FeatureList::IsEnabled(
+ features::kAllowJITInstallationWhenAppIconIsMissing)) {
std::string error_message = base::ReplaceStringPlaceholders(
errors::kInvalidWebAppIcon, {web_app_manifest_url.spec()}, nullptr);
SetFirstError(error_message);
@@ -340,7 +362,9 @@ bool InstallablePaymentAppCrawler::CompleteAndStorePaymentWebAppInfoIfValid(
downloader_->FindTestServerURL(GURL(app_info->sw_scope)).spec();
}
- installable_apps_[method_manifest_url] = std::move(app_info);
+ if (crawling_mode_ == CrawlingMode::kJustInTimeInstallation) {
+ installable_apps_[method_manifest_url] = std::move(app_info);
+ }
return true;
}
@@ -437,8 +461,8 @@ bool InstallablePaymentAppCrawler::DownloadAndDecodeWebAppIcon(
content::WebContents::FromRenderFrameHost(render_frame_host) !=
web_contents()) {
// Post the result back asynchronously.
- base::PostTask(
- FROM_HERE, {content::BrowserThread::UI},
+ content::GetUIThreadTaskRunner({})->PostTask(
+ FROM_HERE,
base::BindOnce(
&InstallablePaymentAppCrawler::FinishCrawlingPaymentAppsIfReady,
weak_ptr_factory_.GetWeakPtr()));
@@ -464,20 +488,41 @@ void InstallablePaymentAppCrawler::OnPaymentWebAppIconDownloadAndDecoded(
const GURL& web_app_manifest_url,
const SkBitmap& icon) {
number_of_web_app_icons_to_download_and_decode_--;
- auto it = installable_apps_.find(method_manifest_url);
- DCHECK(it != installable_apps_.end());
- DCHECK(IsSameOriginWith(GURL(it->second->sw_scope), web_app_manifest_url));
- if (icon.drawsNothing()) {
- log_.Error(
- "Failed to download or decode the icon from web app manifest \"" +
- web_app_manifest_url.spec() + "\" for payment handler manifest \"" +
- method_manifest_url.spec() + "\".");
- std::string error_message = base::ReplaceStringPlaceholders(
- errors::kInvalidWebAppIcon, {web_app_manifest_url.spec()}, nullptr);
- SetFirstError(error_message);
- installable_apps_.erase(it);
+
+ if (crawling_mode_ == CrawlingMode::kJustInTimeInstallation) {
+ auto it = installable_apps_.find(method_manifest_url);
+ DCHECK(it != installable_apps_.end());
+ DCHECK(IsSameOriginWith(GURL(it->second->sw_scope), web_app_manifest_url));
+ if (icon.drawsNothing() &&
+ !base::FeatureList::IsEnabled(
+ features::kAllowJITInstallationWhenAppIconIsMissing)) {
+ log_.Error(
+ "Failed to download or decode the icon from web app manifest \"" +
+ web_app_manifest_url.spec() + "\" for payment handler manifest \"" +
+ method_manifest_url.spec() + "\".");
+ std::string error_message = base::ReplaceStringPlaceholders(
+ errors::kInvalidWebAppIcon, {web_app_manifest_url.spec()}, nullptr);
+ SetFirstError(error_message);
+ installable_apps_.erase(it);
+ } else {
+ it->second->icon = std::make_unique<SkBitmap>(icon);
+ }
} else {
- it->second->icon = std::make_unique<SkBitmap>(icon);
+ DCHECK_EQ(CrawlingMode::kMissingIconRefetch, crawling_mode_);
+ auto it = method_manifest_urls_for_icon_refetch_.find(method_manifest_url);
+ DCHECK(it != method_manifest_urls_for_icon_refetch_.end());
+ if (icon.drawsNothing()) {
+ log_.Warn("Failed to refetch a valid icon from web app manifest \"" +
+ web_app_manifest_url.spec() +
+ "\" for payment handler manifest \"" +
+ method_manifest_url.spec() + "\".");
+ } else {
+ auto refetched_icon = std::make_unique<RefetchedIcon>();
+ refetched_icon->method_name = method_manifest_url.spec();
+ refetched_icon->icon = std::make_unique<SkBitmap>(icon);
+ refetched_icons_.insert(
+ std::make_pair(web_app_manifest_url, std::move(refetched_icon)));
+ }
}
FinishCrawlingPaymentAppsIfReady();
@@ -492,7 +537,8 @@ void InstallablePaymentAppCrawler::FinishCrawlingPaymentAppsIfReady() {
return;
}
- std::move(callback_).Run(std::move(installable_apps_), first_error_message_);
+ std::move(callback_).Run(std::move(installable_apps_),
+ std::move(refetched_icons_), first_error_message_);
std::move(finished_using_resources_).Run();
}
diff --git a/chromium/components/payments/content/installable_payment_app_crawler.h b/chromium/components/payments/content/installable_payment_app_crawler.h
index 45437ddd436..71861a8e33d 100644
--- a/chromium/components/payments/content/installable_payment_app_crawler.h
+++ b/chromium/components/payments/content/installable_payment_app_crawler.h
@@ -33,6 +33,13 @@ class WebContents;
namespace payments {
+struct RefetchedIcon {
+ RefetchedIcon();
+ ~RefetchedIcon();
+ std::string method_name;
+ std::unique_ptr<SkBitmap> icon;
+};
+
// Crawls installable web payment apps. First, fetches and parses the payment
// method manifests to get 'default_applications' manifest urls. Then, fetches
// and parses the web app manifests to get the installable payment apps' info.
@@ -40,8 +47,18 @@ class InstallablePaymentAppCrawler : public content::WebContentsObserver {
public:
using FinishedCrawlingCallback = base::OnceCallback<void(
std::map<GURL, std::unique_ptr<WebAppInstallationInfo>>,
+ std::map<GURL, std::unique_ptr<RefetchedIcon>>,
const std::string& error_message)>;
+ enum class CrawlingMode {
+ // In this mode the crawler will crawl for finding JIT installable payment
+ // apps.
+ kJustInTimeInstallation,
+ // In this mode the crawler will crawl for downloading missing icons for
+ // already installed payment apps.
+ kMissingIconRefetch,
+ };
+
// |merchant_origin| is the origin of the iframe that created the
// PaymentRequest object. It is used by security features like
// 'Sec-Fetch-Site' and 'Cross-Origin-Resource-Policy'.
@@ -66,14 +83,15 @@ class InstallablePaymentAppCrawler : public content::WebContentsObserver {
// then this object is safe to be deleted.
void Start(
const std::vector<mojom::PaymentMethodDataPtr>& requested_method_data,
+ std::set<GURL> method_manifest_urls_for_icon_refetch,
FinishedCrawlingCallback callback,
base::OnceClosure finished_using_resources);
void IgnorePortInOriginComparisonForTesting();
- private:
bool IsSameOriginWith(const GURL& a, const GURL& b);
+ private:
void OnPaymentMethodManifestDownloaded(
const GURL& method_manifest_url,
const GURL& method_manifest_url_after_redirects,
@@ -127,6 +145,8 @@ class InstallablePaymentAppCrawler : public content::WebContentsObserver {
size_t number_of_web_app_icons_to_download_and_decode_;
std::set<GURL> downloaded_web_app_manifests_;
std::map<GURL, std::unique_ptr<WebAppInstallationInfo>> installable_apps_;
+ std::map<GURL, std::unique_ptr<RefetchedIcon>> refetched_icons_;
+ std::set<GURL> method_manifest_urls_for_icon_refetch_;
// The first error message (if any) to be forwarded to the merchant when
// rejecting the promise returned from PaymentRequest.show().
@@ -134,6 +154,8 @@ class InstallablePaymentAppCrawler : public content::WebContentsObserver {
bool ignore_port_in_origin_comparison_for_testing_ = false;
+ CrawlingMode crawling_mode_ = CrawlingMode::kJustInTimeInstallation;
+
base::WeakPtrFactory<InstallablePaymentAppCrawler> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(InstallablePaymentAppCrawler);
diff --git a/chromium/components/payments/content/manifest_verifier.cc b/chromium/components/payments/content/manifest_verifier.cc
index 112607b1294..9529b48a2e2 100644
--- a/chromium/components/payments/content/manifest_verifier.cc
+++ b/chromium/components/payments/content/manifest_verifier.cc
@@ -280,8 +280,7 @@ void ManifestVerifier::RemoveInvalidPaymentApps() {
method.GetOrigin().spec() +
"\" and the \"supported_origins\" field in the payment method "
"manifest for \"" +
- method.spec() +
- "\" is not \"*\" and is not a list that includes \"" +
+ method.spec() + "\" is not a list that includes \"" +
app_origin + "\".");
}
}
diff --git a/chromium/components/payments/content/payment_app.cc b/chromium/components/payments/content/payment_app.cc
new file mode 100644
index 00000000000..594eba178bb
--- /dev/null
+++ b/chromium/components/payments/content/payment_app.cc
@@ -0,0 +1,182 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/payments/content/payment_app.h"
+
+#include <algorithm>
+
+#include "base/callback.h"
+#include "components/autofill/core/common/autofill_clock.h"
+#include "components/payments/content/autofill_payment_app.h"
+#include "components/payments/core/features.h"
+#include "components/payments/core/payments_experimental_features.h"
+
+namespace payments {
+namespace {
+
+// Returns the sorting group of a payment app. This is used to order payment
+// apps in the order of:
+// 1. Built-in 1st-party payment handlers.
+// 2. Installed 3rd-party payment handlers
+// 3. Complete autofill instruments
+// 4. Just-in-time installable payment handlers that is not yet installed.
+// 5. Incomplete autofill instruments
+int GetSortingGroup(const PaymentApp& app) {
+ switch (app.type()) {
+ case PaymentApp::Type::INTERNAL:
+ return 1;
+ case PaymentApp::Type::SERVICE_WORKER_APP:
+ case PaymentApp::Type::NATIVE_MOBILE_APP:
+ // If the experimental feature is enabled, sort 3rd-party payment handlers
+ // that needs installation below autofill instruments.
+ if (app.NeedsInstallation() &&
+ PaymentsExperimentalFeatures::IsEnabled(
+ features::kDownRankJustInTimePaymentApp)) {
+ return 4;
+ }
+ return 2;
+ case PaymentApp::Type::AUTOFILL:
+ if (app.IsCompleteForPayment()) {
+ return 3;
+ }
+ return 5;
+ case PaymentApp::Type::UNDEFINED:
+ NOTREACHED();
+ return 99;
+ }
+}
+} // namespace
+
+PaymentApp::PaymentApp(int icon_resource_id, Type type)
+ : icon_resource_id_(icon_resource_id), type_(type) {}
+
+PaymentApp::~PaymentApp() {}
+
+const SkBitmap* PaymentApp::icon_bitmap() const {
+ return nullptr;
+}
+
+std::string PaymentApp::GetApplicationIdentifierToHide() const {
+ return std::string();
+}
+
+std::set<std::string> PaymentApp::GetApplicationIdentifiersThatHideThisApp()
+ const {
+ return std::set<std::string>();
+}
+
+bool PaymentApp::IsReadyForMinimalUI() const {
+ return false;
+}
+
+std::string PaymentApp::GetAccountBalance() const {
+ return std::string();
+}
+
+void PaymentApp::DisableShowingOwnUI() {}
+
+void PaymentApp::IsValidForPaymentMethodIdentifier(
+ const std::string& payment_method_identifier,
+ bool* is_valid) const {
+ *is_valid = base::Contains(app_method_names_, payment_method_identifier);
+}
+
+const std::set<std::string>& PaymentApp::GetAppMethodNames() const {
+ return app_method_names_;
+}
+
+ukm::SourceId PaymentApp::UkmSourceId() {
+ return ukm::kInvalidSourceId;
+}
+
+bool PaymentApp::IsWaitingForPaymentDetailsUpdate() const {
+ return false;
+}
+
+void PaymentApp::AbortPaymentApp(
+ base::OnceCallback<void(bool)> abort_callback) {
+ std::move(abort_callback).Run(/*aborted=*/false);
+}
+
+// static
+void PaymentApp::SortApps(std::vector<std::unique_ptr<PaymentApp>>* apps) {
+ DCHECK(apps);
+ std::sort(apps->begin(), apps->end(),
+ [](const std::unique_ptr<PaymentApp>& lhs,
+ const std::unique_ptr<PaymentApp>& rhs) { return *lhs < *rhs; });
+}
+
+// static
+void PaymentApp::SortApps(std::vector<PaymentApp*>* apps) {
+ DCHECK(apps);
+ std::sort(
+ apps->begin(), apps->end(),
+ [](const PaymentApp* lhs, const PaymentApp* rhs) { return *lhs < *rhs; });
+}
+
+bool PaymentApp::operator<(const PaymentApp& other) const {
+ int sorting_group = GetSortingGroup(*this);
+ int other_sorting_group = GetSortingGroup(other);
+
+ // First sort payment apps by their sorting group.
+ if (sorting_group != other_sorting_group) {
+ return sorting_group < other_sorting_group;
+ }
+
+ // Within a group, compare by completeness.
+ // Non-autofill apps have max completeness score. Autofill cards are sorted
+ // based on completeness. (Each autofill card is considered an app.)
+ int completeness = GetCompletenessScore() - other.GetCompletenessScore();
+ if (completeness != 0)
+ return completeness > 0;
+
+ // Sort autofill cards using their frecency scores as tie breaker.
+ if (type_ == Type::AUTOFILL) {
+ DCHECK_EQ(other.type(), Type::AUTOFILL);
+ return static_cast<const AutofillPaymentApp*>(this)
+ ->credit_card()
+ ->HasGreaterFrecencyThan(
+ static_cast<const AutofillPaymentApp*>(&other)->credit_card(),
+ autofill::AutofillClock::Now());
+ }
+
+ // SW based payment apps are sorted based on whether they will handle shipping
+ // delegation or not (i.e. shipping address is requested and the app supports
+ // the delegation.).
+ if (HandlesShippingAddress() != other.HandlesShippingAddress())
+ return HandlesShippingAddress();
+
+ // SW based payment apps are sorted based on the number of the contact field
+ // delegations that they will handle (i.e. number of contact fields which are
+ // requested and the apps support their delegations.)
+ int supported_contact_delegations_num = 0;
+ if (HandlesPayerEmail())
+ supported_contact_delegations_num++;
+ if (HandlesPayerName())
+ supported_contact_delegations_num++;
+ if (HandlesPayerPhone())
+ supported_contact_delegations_num++;
+
+ int other_supported_contact_delegations_num = 0;
+ if (other.HandlesPayerEmail())
+ other_supported_contact_delegations_num++;
+ if (other.HandlesPayerName())
+ other_supported_contact_delegations_num++;
+ if (other.HandlesPayerPhone())
+ other_supported_contact_delegations_num++;
+
+ int contact_delegations_diff = supported_contact_delegations_num -
+ other_supported_contact_delegations_num;
+ if (contact_delegations_diff != 0)
+ return contact_delegations_diff > 0;
+
+ // SW based payment apps are sorted based on whether they can be pre-selected
+ // or not. Note that autofill cards are already sorted by CanPreselect() since
+ // they are sorted by completeness and type matching.
+ if (CanPreselect() != other.CanPreselect())
+ return CanPreselect();
+ return false;
+}
+
+} // namespace payments
diff --git a/chromium/components/payments/content/payment_app.h b/chromium/components/payments/content/payment_app.h
new file mode 100644
index 00000000000..570038c36b9
--- /dev/null
+++ b/chromium/components/payments/content/payment_app.h
@@ -0,0 +1,204 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PAYMENTS_CONTENT_PAYMENT_APP_H_
+#define COMPONENTS_PAYMENTS_CONTENT_PAYMENT_APP_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string16.h"
+#include "build/build_config.h"
+#include "components/autofill/core/browser/data_model/credit_card.h"
+#include "components/payments/core/payer_data.h"
+#include "services/metrics/public/cpp/ukm_source_id.h"
+#include "third_party/blink/public/mojom/payments/payment_app.mojom.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+namespace payments {
+
+class PaymentHandlerHost;
+
+// Base class which represents a payment app in Payment Request.
+class PaymentApp {
+ public:
+ // The type of this app instance.
+ // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.components.payments
+ // GENERATED_JAVA_CLASS_NAME_OVERRIDE: PaymentAppType
+ enum class Type {
+ // Undefined type of payment app. Can be used for setting the default return
+ // value of an abstract class or an interface.
+ UNDEFINED,
+ // The payment app built into the browser that uses the autofill data.
+ AUTOFILL,
+ // A 3rd-party platform-specific mobile app, such as an Android app
+ // integrated via
+ // https://developers.google.com/web/fundamentals/payments/payment-apps-developer-guide/android-payment-apps
+ NATIVE_MOBILE_APP,
+ // A 3rd-party cross-platform service worked based payment app.
+ SERVICE_WORKER_APP,
+ // An internal 1st-party payment app, e.g., Google Pay on Chrome or Samsung
+ // Pay on Samsung Internet.
+ INTERNAL,
+ };
+
+ class Delegate {
+ public:
+ virtual ~Delegate() {}
+
+ // Should be called with method name (e.g., "https://google.com/pay") and
+ // json-serialized stringified details.
+ virtual void OnInstrumentDetailsReady(
+ const std::string& method_name,
+ const std::string& stringified_details,
+ const PayerData& payer_data) = 0;
+
+ // Should be called with a developer-facing error message to be used when
+ // rejecting PaymentRequest.show().
+ virtual void OnInstrumentDetailsError(const std::string& error_message) = 0;
+ };
+
+ virtual ~PaymentApp();
+
+ // Will call into the |delegate| (can't be null) on success or error.
+ virtual void InvokePaymentApp(Delegate* delegate) = 0;
+ // Called when the payment app window has closed.
+ virtual void OnPaymentAppWindowClosed() {}
+ // Returns whether the app is complete to be used for payment without further
+ // editing.
+ virtual bool IsCompleteForPayment() const = 0;
+ // Returns the calculated completeness score. Used to sort the list of
+ // available apps.
+ virtual uint32_t GetCompletenessScore() const = 0;
+ // Returns whether the app can be preselected in the payment sheet. If none of
+ // the apps can be preselected, the user must explicitly select an app from a
+ // list.
+ virtual bool CanPreselect() const = 0;
+ // Returns a message to indicate to the user what's missing for the app to be
+ // complete for payment.
+ virtual base::string16 GetMissingInfoLabel() const = 0;
+ // Returns this app's answer for PaymentRequest.hasEnrolledInstrument().
+ virtual bool HasEnrolledInstrument() const = 0;
+ // Records the use of this payment app.
+ virtual void RecordUse() = 0;
+ // Check whether this payment app needs installation before it can be used.
+ virtual bool NeedsInstallation() const = 0;
+
+ // The non-human readable identifier for this payment app. For example, the
+ // GUID of an autofill card or the scope of a payment handler.
+ virtual std::string GetId() const = 0;
+
+ // Return the sub/label of payment app, to be displayed to the user.
+ virtual base::string16 GetLabel() const = 0;
+ virtual base::string16 GetSublabel() const = 0;
+
+ // Returns the icon bitmap or null.
+ virtual const SkBitmap* icon_bitmap() const;
+
+ // Returns the identifier for another payment app that should be hidden when
+ // this payment app is present.
+ virtual std::string GetApplicationIdentifierToHide() const;
+
+ // Returns the set of identifier of other apps that would cause this app to be
+ // hidden, if any of them are present, e.g., ["com.bobpay.production",
+ // "com.bobpay.beta"].
+ virtual std::set<std::string> GetApplicationIdentifiersThatHideThisApp()
+ const;
+
+ // Whether the payment app is ready for minimal UI flow.
+ virtual bool IsReadyForMinimalUI() const;
+
+ // The account balance of the payment app that is ready for a minimal UI flow.
+ virtual std::string GetAccountBalance() const;
+
+ // Disable opening a window for this payment app. Used in minimal UI flow.
+ virtual void DisableShowingOwnUI();
+
+ // Returns true if this payment app can be used to fulfill a request
+ // specifying |method| as supported method of payment. The parsed basic-card
+ // specific data (supported_networks) is relevant only for the
+ // AutofillPaymentApp, which runs inside of the browser process and thus
+ // should not be parsing untrusted JSON strings from the renderer.
+ virtual bool IsValidForModifier(
+ const std::string& method,
+ bool supported_networks_specified,
+ const std::set<std::string>& supported_networks) const = 0;
+
+ // Sets |is_valid| to true if this payment app can handle payments for the
+ // given |payment_method_identifier|. The |is_valid| is an out-param instead
+ // of the return value to enable binding this method with a base::WeakPtr,
+ // which prohibits non-void methods.
+ void IsValidForPaymentMethodIdentifier(
+ const std::string& payment_method_identifier,
+ bool* is_valid) const;
+
+ // Returns a WeakPtr to this payment app.
+ virtual base::WeakPtr<PaymentApp> AsWeakPtr() = 0;
+
+ // Returns true if this payment app can collect and return the required
+ // information. This is used to show/hide shipping/contact sections in payment
+ // sheet view depending on the selected app.
+ virtual bool HandlesShippingAddress() const = 0;
+ virtual bool HandlesPayerName() const = 0;
+ virtual bool HandlesPayerEmail() const = 0;
+ virtual bool HandlesPayerPhone() const = 0;
+
+ // Returns the set of payment methods supported by this app.
+ const std::set<std::string>& GetAppMethodNames() const;
+
+ // Sorts the apps using the overloaded < operator.
+ static void SortApps(std::vector<std::unique_ptr<PaymentApp>>* apps);
+ static void SortApps(std::vector<PaymentApp*>* apps);
+
+ int icon_resource_id() const { return icon_resource_id_; }
+ Type type() const { return type_; }
+
+ virtual ukm::SourceId UkmSourceId();
+
+ // Optionally bind to the Mojo pipe for receiving events generated by the
+ // invoked payment handler.
+ virtual void SetPaymentHandlerHost(
+ base::WeakPtr<PaymentHandlerHost> payment_handler_host) {}
+
+ // Whether the payment app is waiting for the merchant to update the purchase
+ // price based on the shipping, billing, or contact information that the user
+ // has selected inside of the payment app.
+ virtual bool IsWaitingForPaymentDetailsUpdate() const;
+
+ // Notifies the payment app of the updated details, such as updated total, in
+ // response to the change of any of the following: payment method, shipping
+ // address, or shipping option.
+ virtual void UpdateWith(
+ mojom::PaymentRequestDetailsUpdatePtr details_update) {}
+
+ // Notifies the payment app that the merchant did not handle the payment
+ // method, shipping option, or shipping address change events, so the payment
+ // details are unchanged.
+ virtual void OnPaymentDetailsNotUpdated() {}
+
+ // Requests the invoked payment app to abort if possible. Only called if this
+ // payment app is currently invoked.
+ virtual void AbortPaymentApp(base::OnceCallback<void(bool)> abort_callback);
+
+ protected:
+ PaymentApp(int icon_resource_id, Type type);
+
+ // The set of payment methods supported by this app.
+ std::set<std::string> app_method_names_;
+
+ private:
+ bool operator<(const PaymentApp& other) const;
+ int icon_resource_id_;
+ Type type_;
+
+ DISALLOW_COPY_AND_ASSIGN(PaymentApp);
+};
+
+} // namespace payments
+
+#endif // COMPONENTS_PAYMENTS_CONTENT_PAYMENT_APP_H_
diff --git a/chromium/components/payments/content/payment_app_factory.h b/chromium/components/payments/content/payment_app_factory.h
index ccd4f993448..be8a7731ffd 100644
--- a/chromium/components/payments/content/payment_app_factory.h
+++ b/chromium/components/payments/content/payment_app_factory.h
@@ -11,8 +11,8 @@
#include "base/callback_forward.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
+#include "components/payments/content/payment_app.h"
#include "components/payments/content/service_worker_payment_app_finder.h"
-#include "components/payments/core/payment_app.h"
#include "content/public/browser/payment_app_provider.h"
#include "third_party/blink/public/mojom/payments/payment_request.mojom.h"
@@ -54,19 +54,19 @@ class PaymentAppFactory {
virtual scoped_refptr<PaymentManifestWebDataService>
GetPaymentManifestWebDataService() const = 0;
virtual bool MayCrawlForInstallablePaymentApps() = 0;
+ virtual bool IsOffTheRecord() const = 0;
+ virtual PaymentRequestSpec* GetSpec() const = 0;
+
+ // Tells the UI to show the processing spinner. Only desktop UI needs this
+ // notification.
+ virtual void ShowProcessingSpinner() = 0;
- // These parameters are only used to create native payment apps.
+ // These parameters are only used to create the autofill payment app.
virtual const std::vector<autofill::AutofillProfile*>&
GetBillingProfiles() = 0;
virtual bool IsRequestedAutofillDataAvailable() = 0;
virtual ContentPaymentRequestDelegate* GetPaymentRequestDelegate()
const = 0;
- virtual PaymentRequestSpec* GetSpec() const = 0;
-
- // Called after an app is installed. Used for just-in-time installable
- // payment handlers, for example.
- virtual void OnPaymentAppInstalled(const url::Origin& origin,
- int64_t registration_id) = 0;
// Called when an app is created.
virtual void OnPaymentAppCreated(std::unique_ptr<PaymentApp> app) = 0;
@@ -78,16 +78,10 @@ class PaymentAppFactory {
// Whether the factory should early exit before creating platform-specific
// PaymentApp objects. This is used by PaymentAppServiceBridge to skip
- // creating native PaymentApps, which currently cannot be used over JNI.
+ // creating native AutofillPaymentApp, which currently cannot be used over
+ // JNI.
virtual bool SkipCreatingNativePaymentApps() const = 0;
- // When SkipCreatingNativePaymentApps() is true, this callback is called
- // when service-worker payment app info is available.
- virtual void OnCreatingNativePaymentAppsSkipped(
- content::PaymentAppProvider::PaymentApps apps,
- ServiceWorkerPaymentAppFinder::InstallablePaymentApps
- installable_apps) = 0;
-
// Called when all apps of this factory have been created.
virtual void OnDoneCreatingPaymentApps() = 0;
};
diff --git a/chromium/components/payments/content/payment_app_service.cc b/chromium/components/payments/content/payment_app_service.cc
index b0631dd395b..1dc68fb0b2c 100644
--- a/chromium/components/payments/content/payment_app_service.cc
+++ b/chromium/components/payments/content/payment_app_service.cc
@@ -6,8 +6,8 @@
#include "base/feature_list.h"
#include "components/payments/content/autofill_payment_app_factory.h"
+#include "components/payments/content/payment_app.h"
#include "components/payments/content/service_worker_payment_app_factory.h"
-#include "components/payments/core/payment_app.h"
#include "content/public/common/content_features.h"
namespace payments {
@@ -32,4 +32,8 @@ void PaymentAppService::Create(
}
}
+void PaymentAppService::Shutdown() {
+ factories_.clear();
+}
+
} // namespace payments
diff --git a/chromium/components/payments/content/payment_app_service.h b/chromium/components/payments/content/payment_app_service.h
index cea8f09d206..a26ce3ef423 100644
--- a/chromium/components/payments/content/payment_app_service.h
+++ b/chromium/components/payments/content/payment_app_service.h
@@ -30,6 +30,9 @@ class PaymentAppService : public KeyedService {
// Create payment apps for |delegate|.
void Create(base::WeakPtr<PaymentAppFactory::Delegate> delegate);
+ // KeyedService implementation:
+ void Shutdown() override;
+
private:
std::vector<std::unique_ptr<PaymentAppFactory>> factories_;
diff --git a/chromium/components/payments/content/payment_app_unittest.cc b/chromium/components/payments/content/payment_app_unittest.cc
index 5d982a6f4ff..4e190a3ff17 100644
--- a/chromium/components/payments/content/payment_app_unittest.cc
+++ b/chromium/components/payments/content/payment_app_unittest.cc
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "components/payments/core/payment_app.h"
+#include "components/payments/content/payment_app.h"
#include <vector>
@@ -11,10 +11,9 @@
#include "components/autofill/core/browser/autofill_test_utils.h"
#include "components/autofill/core/browser/data_model/autofill_profile.h"
#include "components/autofill/core/browser/data_model/credit_card.h"
+#include "components/payments/content/autofill_payment_app.h"
#include "components/payments/content/service_worker_payment_app.h"
-#include "components/payments/core/autofill_payment_app.h"
#include "components/payments/core/features.h"
-#include "components/payments/core/mock_payment_request_delegate.h"
#include "content/public/browser/stored_payment_app.h"
#include "content/public/browser/supported_delegations.h"
#include "content/public/browser/web_contents.h"
@@ -86,12 +85,10 @@ class PaymentAppTest : public testing::TestWithParam<RequiredPaymentOptions>,
}
return std::make_unique<ServiceWorkerPaymentApp>(
- &browser_context_, GURL("https://testmerchant.com"),
+ web_contents_, GURL("https://testmerchant.com"),
GURL("https://testmerchant.com/bobpay"), spec_.get(),
- std::move(stored_app), &delegate_,
- /*identity_callback=*/
- base::BindRepeating([](const url::Origin&,
- int64_t) { /* Intentionally left blank. */ }));
+ std::move(stored_app), /*is_incognito=*/false,
+ /*show_processing_spinner=*/base::DoNothing());
}
std::unique_ptr<ServiceWorkerPaymentApp>
@@ -119,10 +116,8 @@ class PaymentAppTest : public testing::TestWithParam<RequiredPaymentOptions>,
return std::make_unique<ServiceWorkerPaymentApp>(
web_contents_, GURL("https://merchant.example"),
GURL("https://merchant.example/iframe"), spec_.get(),
- std::move(installable_app), "https://pay.example", &delegate_,
- /*identity_callback=*/
- base::BindRepeating([](const url::Origin&,
- int64_t) { /* Intentionally left blank. */ }));
+ std::move(installable_app), "https://pay.example",
+ /*is_incognito=*/false, /*show_processing_spinner=*/base::DoNothing());
}
static void PopulateIcon(SkBitmap* icon) {
@@ -178,7 +173,6 @@ class PaymentAppTest : public testing::TestWithParam<RequiredPaymentOptions>,
autofill::AutofillProfile address_;
autofill::CreditCard local_card_;
std::vector<autofill::AutofillProfile*> billing_profiles_;
- MockPaymentRequestDelegate delegate_;
RequiredPaymentOptions required_options_;
std::unique_ptr<PaymentRequestSpec> spec_;
@@ -207,8 +201,8 @@ TEST_P(PaymentAppTest, SortApps) {
// Add an expired card.
autofill::CreditCard expired_card = local_credit_card();
expired_card.SetExpirationYear(2016);
- AutofillPaymentApp expired_cc_app("visa", expired_card,
- billing_profiles(), "en-US", nullptr);
+ AutofillPaymentApp expired_cc_app("visa", expired_card, billing_profiles(),
+ "en-US", nullptr);
apps.push_back(&expired_cc_app);
// Add a non-preselectable sw based payment app.
diff --git a/chromium/components/payments/content/payment_handler_host.cc b/chromium/components/payments/content/payment_handler_host.cc
index f1ac47304e4..4779d9b5b11 100644
--- a/chromium/components/payments/content/payment_handler_host.cc
+++ b/chromium/components/payments/content/payment_handler_host.cc
@@ -124,7 +124,7 @@ void PaymentHandlerHost::UpdateWith(
data.emplace(prefix + " Method Name",
modifier->method_data->method_name);
data.emplace(prefix + " Method Data",
- modifier->method_data->stringified_data);
+ modifier->method_data->stringified_data.value_or("{}"));
if (!modifier->total)
continue;
data.emplace(prefix + " Total Currency", modifier->total->currency);
@@ -187,8 +187,10 @@ void PaymentHandlerHost::ChangePaymentMethod(
return;
}
+ const std::string stringified_data =
+ method_data->stringified_data.value_or("{}");
if (!delegate_->ChangePaymentMethod(method_data->method_name,
- method_data->stringified_data)) {
+ stringified_data)) {
RunCallbackWithError(errors::kInvalidState, std::move(callback));
return;
}
@@ -202,7 +204,7 @@ void PaymentHandlerHost::ChangePaymentMethod(
"Change payment method",
/*instance_id=*/payment_request_id_for_logs_,
{{"Method Name", method_data->method_name},
- {"Method Data", method_data->stringified_data}});
+ {"Method Data", stringified_data}});
}
change_payment_request_details_callback_ = std::move(callback);
diff --git a/chromium/components/payments/content/payment_handler_host.h b/chromium/components/payments/content/payment_handler_host.h
index b0900ac70c7..c3814c48489 100644
--- a/chromium/components/payments/content/payment_handler_host.h
+++ b/chromium/components/payments/content/payment_handler_host.h
@@ -130,7 +130,8 @@ class PaymentHandlerHost : public mojom::PaymentHandlerHost {
// The merchant page that invoked the Payment Request API.
content::WebContents* web_contents_;
- // Not null and outlives this object. Owns this object.
+ // Not null and outlives this object. Either owns this object or is owned by
+ // the owner of this object.
Delegate* delegate_;
// The origin of the payment handler / service worker registration scope. Used
diff --git a/chromium/components/payments/content/payment_request.cc b/chromium/components/payments/content/payment_request.cc
index dd5ee3b26fd..34323322dc5 100644
--- a/chromium/components/payments/content/payment_request.cc
+++ b/chromium/components/payments/content/payment_request.cc
@@ -14,6 +14,7 @@
#include "base/strings/string_util.h"
#include "components/payments/content/can_make_payment_query_factory.h"
#include "components/payments/content/content_payment_request_delegate.h"
+#include "components/payments/content/payment_app.h"
#include "components/payments/content/payment_details_converter.h"
#include "components/payments/content/payment_request_converter.h"
#include "components/payments/content/payment_request_web_contents_manager.h"
@@ -23,7 +24,6 @@
#include "components/payments/core/features.h"
#include "components/payments/core/method_strings.h"
#include "components/payments/core/native_error_strings.h"
-#include "components/payments/core/payment_app.h"
#include "components/payments/core/payment_details.h"
#include "components/payments/core/payment_details_validation.h"
#include "components/payments/core/payment_prefs.h"
@@ -96,7 +96,7 @@ PaymentRequest::PaymentRequest(
render_frame_host->GetLastCommittedURL())),
frame_security_origin_(render_frame_host->GetLastCommittedOrigin()),
observer_for_testing_(observer_for_testing),
- journey_logger_(delegate_->IsIncognito(),
+ journey_logger_(delegate_->IsOffTheRecord(),
ukm::GetSourceIdForWebContentsDocument(web_contents)) {
receiver_.Bind(std::move(receiver));
// OnConnectionTerminated will be called when the Mojo pipe is closed. This
@@ -186,10 +186,7 @@ void PaymentRequest::Init(
web_contents_, initiator_frame, top_level_origin_, frame_origin_,
frame_security_origin_, spec_.get(),
/*delegate=*/this, delegate_->GetApplicationLocale(),
- delegate_->GetPersonalDataManager(), delegate_.get(),
- base::BindRepeating(&PaymentRequest::SetInvokedServiceWorkerIdentity,
- weak_ptr_factory_.GetWeakPtr()),
- &journey_logger_);
+ delegate_->GetPersonalDataManager(), delegate_.get(), &journey_logger_);
journey_logger_.SetRequestedInformation(
spec_->request_shipping(), spec_->request_payer_email(),
@@ -343,8 +340,8 @@ void PaymentRequest::UpdateWith(mojom::PaymentDetailsPtr details) {
}
if (state()->selected_app() && state()->IsPaymentAppInvoked() &&
- payment_handler_host_.is_waiting_for_payment_details_update()) {
- payment_handler_host_.UpdateWith(
+ state()->selected_app()->IsWaitingForPaymentDetailsUpdate()) {
+ state()->selected_app()->UpdateWith(
PaymentDetailsConverter::ConvertToPaymentRequestDetailsUpdate(
details, state()->selected_app()->HandlesShippingAddress(),
base::BindRepeating(&PaymentApp::IsValidForPaymentMethodIdentifier,
@@ -386,9 +383,9 @@ void PaymentRequest::OnPaymentDetailsNotUpdated() {
spec_->RecomputeSpecForDetails();
- if (state()->IsPaymentAppInvoked() &&
- payment_handler_host_.is_waiting_for_payment_details_update()) {
- payment_handler_host_.OnPaymentDetailsNotUpdated();
+ if (state()->IsPaymentAppInvoked() && state()->selected_app() &&
+ state()->selected_app()->IsWaitingForPaymentDetailsUpdate()) {
+ state()->selected_app()->OnPaymentDetailsNotUpdated();
}
}
@@ -412,18 +409,16 @@ void PaymentRequest::Abort() {
// The abort is only successful if the payment app wasn't yet invoked.
// TODO(crbug.com/716546): Add a merchant abort metric
- bool accepting_abort = !state_->IsPaymentAppInvoked();
- if (accepting_abort)
- RecordFirstAbortReason(JourneyLogger::ABORT_REASON_ABORTED_BY_MERCHANT);
-
- if (client_.is_bound())
- client_->OnAbort(accepting_abort);
-
if (observer_for_testing_)
observer_for_testing_->OnAbortCalled();
- if (accepting_abort)
- state_->OnAbort();
+ if (!state_->IsPaymentAppInvoked() || !state_->selected_app()) {
+ OnAbortResult(/*aborted=*/true);
+ return;
+ }
+
+ state_->selected_app()->AbortPaymentApp(base::BindOnce(
+ &PaymentRequest::OnAbortResult, weak_ptr_factory_.GetWeakPtr()));
}
void PaymentRequest::Complete(mojom::PaymentComplete result) {
@@ -565,7 +560,7 @@ void PaymentRequest::AreRequestedMethodsSupportedCallback(
bool methods_supported,
const std::string& error_message) {
if (is_show_called_ && observer_for_testing_)
- observer_for_testing_->OnShowAppsReady();
+ observer_for_testing_->OnShowAppsReady(weak_ptr_factory_.GetWeakPtr());
if (methods_supported) {
if (SatisfiesSkipUIConstraints())
@@ -668,7 +663,11 @@ void PaymentRequest::OnPaymentResponseAvailable(
: JourneyLogger::Event::EVENT_SELECTED_OTHER;
break;
}
+ case PaymentApp::Type::UNDEFINED:
+ // Intentionally fall through.
case PaymentApp::Type::NATIVE_MOBILE_APP:
+ // Intentionally fall through.
+ case PaymentApp::Type::INTERNAL:
NOTREACHED();
break;
}
@@ -709,12 +708,6 @@ void PaymentRequest::OnPayerInfoSelected(mojom::PayerDetailPtr payer_info) {
client_->OnPayerDetailChange(std::move(payer_info));
}
-void PaymentRequest::SetInvokedServiceWorkerIdentity(const url::Origin& origin,
- int64_t registration_id) {
- payment_handler_host_.set_sw_origin_for_logs(origin);
- payment_handler_host_.set_registration_id_for_logs(registration_id);
-}
-
void PaymentRequest::UserCancelled() {
// If |client_| is not bound, then the object is already being destroyed as
// a result of a renderer event.
@@ -765,10 +758,8 @@ void PaymentRequest::OnConnectionTerminated() {
void PaymentRequest::Pay() {
journey_logger_.SetEventOccurred(JourneyLogger::EVENT_PAY_CLICKED);
DCHECK(state_->selected_app());
- if (state_->selected_app()->type() == PaymentApp::Type::SERVICE_WORKER_APP) {
- static_cast<ServiceWorkerPaymentApp*>(state_->selected_app())
- ->set_payment_handler_host(payment_handler_host_.Bind());
- }
+ state_->selected_app()->SetPaymentHandlerHost(
+ payment_handler_host_.AsWeakPtr());
state_->GeneratePaymentResponse();
}
@@ -776,8 +767,8 @@ void PaymentRequest::HideIfNecessary() {
display_handle_.reset();
}
-bool PaymentRequest::IsIncognito() const {
- return delegate_->IsIncognito();
+bool PaymentRequest::IsOffTheRecord() const {
+ return delegate_->IsOffTheRecord();
}
void PaymentRequest::OnPaymentHandlerOpenWindowCalled() {
@@ -845,4 +836,14 @@ void PaymentRequest::RespondToHasEnrolledInstrumentQuery(
journey_logger_.SetHasEnrolledInstrumentValue(has_enrolled_instrument);
}
+void PaymentRequest::OnAbortResult(bool aborted) {
+ if (client_.is_bound())
+ client_->OnAbort(aborted);
+
+ if (aborted) {
+ RecordFirstAbortReason(JourneyLogger::ABORT_REASON_ABORTED_BY_MERCHANT);
+ state_->OnAbort();
+ }
+}
+
} // namespace payments
diff --git a/chromium/components/payments/content/payment_request.h b/chromium/components/payments/content/payment_request.h
index c7ca2024fa3..24c759882ba 100644
--- a/chromium/components/payments/content/payment_request.h
+++ b/chromium/components/payments/content/payment_request.h
@@ -35,11 +35,11 @@ class ContentPaymentRequestDelegate;
class PaymentRequestWebContentsManager;
// This class manages the interaction between the renderer (through the
-// PaymentRequestClient and Mojo stub implementation) and the UI (through the
-// PaymentRequestDelegate). The API user (merchant) specification (supported
-// payment methods, required information, order details) is stored in
+// PaymentRequestClient and Mojo stub implementation) and the desktop Payment UI
+// (through the PaymentRequestDelegate). The API user (merchant) specification
+// (supported payment methods, required information, order details) is stored in
// PaymentRequestSpec, and the current user selection state (and related data)
-// is stored in PaymentRequestSpec.
+// is stored in PaymentRequestState.
class PaymentRequest : public mojom::PaymentRequest,
public PaymentHandlerHost::Delegate,
public PaymentRequestSpec::Observer,
@@ -51,7 +51,8 @@ class PaymentRequest : public mojom::PaymentRequest,
virtual void OnCanMakePaymentReturned() = 0;
virtual void OnHasEnrolledInstrumentCalled() = 0;
virtual void OnHasEnrolledInstrumentReturned() = 0;
- virtual void OnShowAppsReady() {}
+ virtual void OnShowAppsReady(
+ base::WeakPtr<PaymentRequest> payment_request) {}
virtual void OnNotSupportedError() = 0;
virtual void OnConnectionTerminated() = 0;
virtual void OnAbortCalled() = 0;
@@ -101,9 +102,6 @@ class PaymentRequest : public mojom::PaymentRequest,
void OnShippingAddressSelected(mojom::PaymentAddressPtr address) override;
void OnPayerInfoSelected(mojom::PayerDetailPtr payer_info) override;
- void SetInvokedServiceWorkerIdentity(const url::Origin& origin,
- int64_t registration_id);
-
// Called when the user explicitly cancelled the flow. Will send a message
// to the renderer which will indirectly destroy this object (through
// OnConnectionTerminated).
@@ -125,7 +123,7 @@ class PaymentRequest : public mojom::PaymentRequest,
// Hide this Payment Request if it's already showing.
void HideIfNecessary();
- bool IsIncognito() const;
+ bool IsOffTheRecord() const;
// Called when the payment handler requests to open a payment handler window.
void OnPaymentHandlerOpenWindowCalled();
@@ -187,6 +185,8 @@ class PaymentRequest : public mojom::PaymentRequest,
void RespondToHasEnrolledInstrumentQuery(bool has_enrolled_instrument,
bool warn_localhost_or_file);
+ void OnAbortResult(bool aborted);
+
content::WebContents* web_contents_;
const content::GlobalFrameRoutingId initiator_frame_routing_id_;
DeveloperConsoleLogger log_;
diff --git a/chromium/components/payments/content/payment_request_spec.cc b/chromium/components/payments/content/payment_request_spec.cc
index 65467baf57e..bbb7c61a19a 100644
--- a/chromium/components/payments/content/payment_request_spec.cc
+++ b/chromium/components/payments/content/payment_request_spec.cc
@@ -12,10 +12,10 @@
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
+#include "components/payments/content/payment_app.h"
#include "components/payments/content/payment_request_converter.h"
#include "components/payments/core/features.h"
#include "components/payments/core/method_strings.h"
-#include "components/payments/core/payment_app.h"
#include "components/payments/core/payment_method_data.h"
#include "components/payments/core/payment_request_data_util.h"
#include "components/payments/core/payments_experimental_features.h"
diff --git a/chromium/components/payments/content/payment_request_state.cc b/chromium/components/payments/content/payment_request_state.cc
index 02a13c34117..8b7640c1937 100644
--- a/chromium/components/payments/content/payment_request_state.cc
+++ b/chromium/components/payments/content/payment_request_state.cc
@@ -18,19 +18,19 @@
#include "components/autofill/core/browser/geo/autofill_country.h"
#include "components/autofill/core/browser/personal_data_manager.h"
#include "components/autofill/core/browser/validation.h"
+#include "components/payments/content/autofill_payment_app.h"
#include "components/payments/content/autofill_payment_app_factory.h"
#include "components/payments/content/content_payment_request_delegate.h"
+#include "components/payments/content/payment_app.h"
#include "components/payments/content/payment_app_service.h"
#include "components/payments/content/payment_app_service_factory.h"
#include "components/payments/content/payment_manifest_web_data_service.h"
#include "components/payments/content/payment_response_helper.h"
#include "components/payments/content/service_worker_payment_app.h"
#include "components/payments/core/autofill_card_validation.h"
-#include "components/payments/core/autofill_payment_app.h"
#include "components/payments/core/error_strings.h"
#include "components/payments/core/features.h"
#include "components/payments/core/method_strings.h"
-#include "components/payments/core/payment_app.h"
#include "components/payments/core/payment_request_data_util.h"
#include "components/payments/core/payments_experimental_features.h"
#include "content/public/browser/render_frame_host.h"
@@ -66,7 +66,6 @@ PaymentRequestState::PaymentRequestState(
const std::string& app_locale,
autofill::PersonalDataManager* personal_data_manager,
ContentPaymentRequestDelegate* payment_request_delegate,
- const ServiceWorkerPaymentApp::IdentityCallback& sw_identity_callback,
JourneyLogger* journey_logger)
: web_contents_(web_contents),
initiator_render_frame_host_(initiator_render_frame_host),
@@ -81,7 +80,6 @@ PaymentRequestState::PaymentRequestState(
are_requested_methods_supported_(
!spec_->supported_card_networks().empty()),
payment_request_delegate_(payment_request_delegate),
- sw_identity_callback_(sw_identity_callback),
profile_comparator_(app_locale, *spec) {
PopulateProfileCache();
@@ -105,6 +103,10 @@ ContentPaymentRequestDelegate* PaymentRequestState::GetPaymentRequestDelegate()
return payment_request_delegate_;
}
+void PaymentRequestState::ShowProcessingSpinner() {
+ GetPaymentRequestDelegate()->ShowProcessingSpinner();
+}
+
PaymentRequestSpec* PaymentRequestState::GetSpec() const {
return spec_;
}
@@ -151,9 +153,8 @@ bool PaymentRequestState::MayCrawlForInstallablePaymentApps() {
!spec_->supports_basic_card();
}
-void PaymentRequestState::OnPaymentAppInstalled(const url::Origin& origin,
- int64_t registration_id) {
- sw_identity_callback_.Run(origin, registration_id);
+bool PaymentRequestState::IsOffTheRecord() const {
+ return GetPaymentRequestDelegate()->IsOffTheRecord();
}
void PaymentRequestState::OnPaymentAppCreated(std::unique_ptr<PaymentApp> app) {
@@ -180,13 +181,6 @@ bool PaymentRequestState::SkipCreatingNativePaymentApps() const {
return false;
}
-void PaymentRequestState::OnCreatingNativePaymentAppsSkipped(
- content::PaymentAppProvider::PaymentApps unused_apps,
- ServiceWorkerPaymentAppFinder::InstallablePaymentApps
- unused_installable_apps) {
- NOTREACHED();
-}
-
void PaymentRequestState::OnDoneCreatingPaymentApps() {
DCHECK_NE(0U, number_of_payment_app_factories_);
if (--number_of_payment_app_factories_ > 0U)
@@ -316,6 +310,14 @@ 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) {
+ get_all_payment_apps_error_ = errors::kAppStoreMethodOnlySupportedInTwa;
+ }
+
std::move(callback).Run(supported, get_all_payment_apps_error_);
}
diff --git a/chromium/components/payments/content/payment_request_state.h b/chromium/components/payments/content/payment_request_state.h
index ce439e9edfd..469b3104402 100644
--- a/chromium/components/payments/content/payment_request_state.h
+++ b/chromium/components/payments/content/payment_request_state.h
@@ -114,24 +114,23 @@ class PaymentRequestState : public PaymentAppFactory::Delegate,
base::OnceCallback<void(bool methods_supported,
const std::string& error_message)>;
- PaymentRequestState(
- content::WebContents* web_contents,
- content::RenderFrameHost* initiator_render_frame_host,
- const GURL& top_level_origin,
- const GURL& frame_origin,
- const url::Origin& frame_security_origin,
- PaymentRequestSpec* spec,
- Delegate* delegate,
- const std::string& app_locale,
- autofill::PersonalDataManager* personal_data_manager,
- ContentPaymentRequestDelegate* payment_request_delegate,
- const ServiceWorkerPaymentApp::IdentityCallback& sw_identity_callback,
- JourneyLogger* journey_logger);
+ PaymentRequestState(content::WebContents* web_contents,
+ content::RenderFrameHost* initiator_render_frame_host,
+ const GURL& top_level_origin,
+ const GURL& frame_origin,
+ const url::Origin& frame_security_origin,
+ PaymentRequestSpec* spec,
+ Delegate* delegate,
+ const std::string& app_locale,
+ autofill::PersonalDataManager* personal_data_manager,
+ ContentPaymentRequestDelegate* payment_request_delegate,
+ JourneyLogger* journey_logger);
~PaymentRequestState() override;
// PaymentAppFactory::Delegate
content::WebContents* GetWebContents() override;
ContentPaymentRequestDelegate* GetPaymentRequestDelegate() const override;
+ void ShowProcessingSpinner() override;
PaymentRequestSpec* GetSpec() const override;
const GURL& GetTopOrigin() override;
const GURL& GetFrameOrigin() override;
@@ -144,15 +143,10 @@ class PaymentRequestState : public PaymentAppFactory::Delegate,
const std::vector<autofill::AutofillProfile*>& GetBillingProfiles() override;
bool IsRequestedAutofillDataAvailable() override;
bool MayCrawlForInstallablePaymentApps() override;
- void OnPaymentAppInstalled(const url::Origin& origin,
- int64_t registration_id) override;
+ bool IsOffTheRecord() const override;
void OnPaymentAppCreated(std::unique_ptr<PaymentApp> app) override;
void OnPaymentAppCreationError(const std::string& error_message) override;
bool SkipCreatingNativePaymentApps() const override;
- void OnCreatingNativePaymentAppsSkipped(
- content::PaymentAppProvider::PaymentApps apps,
- ServiceWorkerPaymentAppFinder::InstallablePaymentApps installable_apps)
- override;
void OnDoneCreatingPaymentApps() override;
// PaymentResponseHelper::Delegate
@@ -416,7 +410,6 @@ class PaymentRequestState : public PaymentAppFactory::Delegate,
std::vector<std::unique_ptr<PaymentApp>> available_apps_;
ContentPaymentRequestDelegate* payment_request_delegate_;
- ServiceWorkerPaymentApp::IdentityCallback sw_identity_callback_;
std::unique_ptr<PaymentResponseHelper> response_helper_;
diff --git a/chromium/components/payments/content/payment_request_state_unittest.cc b/chromium/components/payments/content/payment_request_state_unittest.cc
index 7780428c267..6cfc72c6dbe 100644
--- a/chromium/components/payments/content/payment_request_state_unittest.cc
+++ b/chromium/components/payments/content/payment_request_state_unittest.cc
@@ -34,7 +34,7 @@ class PaymentRequestStateTest : public testing::Test,
PaymentRequestStateTest()
: num_on_selected_information_changed_called_(0),
test_payment_request_delegate_(&test_personal_data_manager_),
- journey_logger_(test_payment_request_delegate_.IsIncognito(),
+ journey_logger_(test_payment_request_delegate_.IsOffTheRecord(),
ukm::UkmRecorder::GetNewSourceID()),
address_(autofill::test::GetFullProfile()),
credit_card_visa_(autofill::test::GetCreditCard()) {
@@ -85,9 +85,6 @@ class PaymentRequestStateTest : public testing::Test,
GURL("https://example.com/pay"),
url::Origin::Create(GURL("https://example.com")), spec_.get(), this,
"en-US", &test_personal_data_manager_, &test_payment_request_delegate_,
- base::BindRepeating(
- [](const url::Origin& origin,
- int64_t registration_id) { /* Intentionally left blank. */ }),
&journey_logger_);
state_->AddObserver(this);
}
diff --git a/chromium/components/payments/content/payment_response_helper.h b/chromium/components/payments/content/payment_response_helper.h
index cdf23a63abf..e3268660821 100644
--- a/chromium/components/payments/content/payment_response_helper.h
+++ b/chromium/components/payments/content/payment_response_helper.h
@@ -11,7 +11,7 @@
#include "base/memory/weak_ptr.h"
#include "components/autofill/core/browser/address_normalizer.h"
#include "components/autofill/core/browser/data_model/autofill_profile.h"
-#include "components/payments/core/payment_app.h"
+#include "components/payments/content/payment_app.h"
#include "third_party/blink/public/mojom/payments/payment_request.mojom.h"
namespace payments {
diff --git a/chromium/components/payments/content/payment_response_helper_unittest.cc b/chromium/components/payments/content/payment_response_helper_unittest.cc
index 96c67a2ffaf..733206de4ba 100644
--- a/chromium/components/payments/content/payment_response_helper_unittest.cc
+++ b/chromium/components/payments/content/payment_response_helper_unittest.cc
@@ -15,8 +15,8 @@
#include "components/autofill/core/browser/data_model/autofill_profile.h"
#include "components/autofill/core/browser/data_model/credit_card.h"
#include "components/autofill/core/browser/test_personal_data_manager.h"
+#include "components/payments/content/autofill_payment_app.h"
#include "components/payments/content/payment_request_spec.h"
-#include "components/payments/core/autofill_payment_app.h"
#include "components/payments/core/test_payment_request_delegate.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/payments/payment_request.mojom.h"
diff --git a/chromium/components/payments/content/service_worker_payment_app.cc b/chromium/components/payments/content/service_worker_payment_app.cc
index be795397b6d..6d51dcaca5d 100644
--- a/chromium/components/payments/content/service_worker_payment_app.cc
+++ b/chromium/components/payments/content/service_worker_payment_app.cc
@@ -14,11 +14,10 @@
#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h"
#include "components/payments/content/payment_event_response_util.h"
+#include "components/payments/content/payment_handler_host.h"
#include "components/payments/content/payment_request_converter.h"
#include "components/payments/core/features.h"
#include "components/payments/core/method_strings.h"
-#include "components/payments/core/payment_request_delegate.h"
-#include "content/public/browser/browser_context.h"
#include "content/public/browser/payment_app_provider.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
@@ -30,41 +29,32 @@ namespace payments {
// Service worker payment app provides icon through bitmap, so set 0 as invalid
// resource Id.
ServiceWorkerPaymentApp::ServiceWorkerPaymentApp(
- content::BrowserContext* browser_context,
+ content::WebContents* web_contents,
const GURL& top_origin,
const GURL& frame_origin,
const PaymentRequestSpec* spec,
std::unique_ptr<content::StoredPaymentApp> stored_payment_app_info,
- PaymentRequestDelegate* payment_request_delegate,
- const IdentityCallback& identity_callback)
+ bool is_incognito,
+ const base::RepeatingClosure& show_processing_spinner)
: PaymentApp(0, PaymentApp::Type::SERVICE_WORKER_APP),
- browser_context_(browser_context),
+ content::WebContentsObserver(web_contents),
top_origin_(top_origin),
frame_origin_(frame_origin),
spec_(spec),
stored_payment_app_info_(std::move(stored_payment_app_info)),
delegate_(nullptr),
- payment_request_delegate_(payment_request_delegate),
- identity_callback_(identity_callback),
+ is_incognito_(is_incognito),
+ show_processing_spinner_(show_processing_spinner),
can_make_payment_result_(false),
has_enrolled_instrument_result_(false),
needs_installation_(false) {
- DCHECK(browser_context_);
+ 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());
-
- if (stored_payment_app_info_->icon) {
- icon_image_ =
- gfx::ImageSkia::CreateFrom1xBitmap(*(stored_payment_app_info_->icon))
- .DeepCopy();
- } else {
- // Create an empty icon image to avoid using invalid icon resource id.
- icon_image_ = gfx::ImageSkia::CreateFrom1xBitmap(SkBitmap()).DeepCopy();
- }
}
// Service worker payment app provides icon through bitmap, so set 0 as invalid
@@ -76,47 +66,35 @@ ServiceWorkerPaymentApp::ServiceWorkerPaymentApp(
const PaymentRequestSpec* spec,
std::unique_ptr<WebAppInstallationInfo> installable_payment_app_info,
const std::string& enabled_method,
- PaymentRequestDelegate* payment_request_delegate,
- const IdentityCallback& identity_callback)
+ bool is_incognito,
+ const base::RepeatingClosure& show_processing_spinner)
: PaymentApp(0, PaymentApp::Type::SERVICE_WORKER_APP),
+ content::WebContentsObserver(web_contents),
top_origin_(top_origin),
frame_origin_(frame_origin),
spec_(spec),
delegate_(nullptr),
- payment_request_delegate_(payment_request_delegate),
- identity_callback_(identity_callback),
+ is_incognito_(is_incognito),
+ show_processing_spinner_(show_processing_spinner),
can_make_payment_result_(false),
has_enrolled_instrument_result_(false),
needs_installation_(true),
- web_contents_(web_contents),
installable_web_app_info_(std::move(installable_payment_app_info)),
installable_enabled_method_(enabled_method) {
- DCHECK(web_contents_);
+ DCHECK(web_contents);
DCHECK(top_origin_.is_valid());
DCHECK(frame_origin_.is_valid());
DCHECK(spec_);
app_method_names_.insert(installable_enabled_method_);
-
- if (installable_web_app_info_->icon) {
- icon_image_ =
- gfx::ImageSkia::CreateFrom1xBitmap(*(installable_web_app_info_->icon))
- .DeepCopy();
- } else {
- // Create an empty icon image to avoid using invalid icon resource id.
- icon_image_ = gfx::ImageSkia::CreateFrom1xBitmap(SkBitmap()).DeepCopy();
- }
}
ServiceWorkerPaymentApp::~ServiceWorkerPaymentApp() {
- if (delegate_ && !needs_installation_) {
+ if (delegate_) {
// If there's a payment in progress, abort it before destroying this so that
// it can update its internal state. Since the PaymentRequest will be
// destroyed, pass an empty callback to the payment app.
- content::PaymentAppProvider::GetInstance()->AbortPayment(
- browser_context_, stored_payment_app_info_->registration_id,
- url::Origin::Create(stored_payment_app_info_->scope),
- *spec_->details().id, base::DoNothing());
+ AbortPaymentApp(/*abort_callback=*/base::DoNothing());
}
}
@@ -130,7 +108,7 @@ void ServiceWorkerPaymentApp::ValidateCanMakePayment(
// Returns true if we are in incognito (avoiding sending the event to the
// payment handler).
- if (payment_request_delegate_->IsIncognito()) {
+ if (is_incognito_) {
OnCanMakePaymentEventSkipped(std::move(callback));
return;
}
@@ -153,7 +131,7 @@ void ServiceWorkerPaymentApp::ValidateCanMakePayment(
}
content::PaymentAppProvider::GetInstance()->CanMakePayment(
- browser_context_, stored_payment_app_info_->registration_id,
+ web_contents(), stored_payment_app_info_->registration_id,
url::Origin::Create(stored_payment_app_info_->scope),
*spec_->details().id, std::move(event_data),
base::BindOnce(&ServiceWorkerPaymentApp::OnCanMakePaymentEventResponded,
@@ -218,6 +196,9 @@ void ServiceWorkerPaymentApp::OnCanMakePaymentEventResponded(
// |can_make_payment| is true as long as there is a matching payment handler.
can_make_payment_result_ = true;
has_enrolled_instrument_result_ = response->can_make_payment;
+ is_ready_for_minimal_ui_ = response->ready_for_minimal_ui;
+ if (response->account_balance)
+ account_balance_ = *response->account_balance;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback), this, can_make_payment_result_));
@@ -228,7 +209,7 @@ void ServiceWorkerPaymentApp::InvokePaymentApp(Delegate* delegate) {
if (needs_installation_) {
content::PaymentAppProvider::GetInstance()->InstallAndInvokePaymentApp(
- web_contents_, CreatePaymentRequestEventData(),
+ web_contents(), CreatePaymentRequestEventData(),
installable_web_app_info_->name,
installable_web_app_info_->icon == nullptr
? SkBitmap()
@@ -248,19 +229,19 @@ void ServiceWorkerPaymentApp::InvokePaymentApp(Delegate* delegate) {
url::Origin::Create(stored_payment_app_info_->scope);
OnPaymentAppIdentity(sw_origin, stored_payment_app_info_->registration_id);
content::PaymentAppProvider::GetInstance()->InvokePaymentApp(
- browser_context_, stored_payment_app_info_->registration_id, sw_origin,
+ web_contents(), stored_payment_app_info_->registration_id, sw_origin,
CreatePaymentRequestEventData(),
base::BindOnce(&ServiceWorkerPaymentApp::OnPaymentAppInvoked,
weak_ptr_factory_.GetWeakPtr()));
}
- payment_request_delegate_->ShowProcessingSpinner();
+ show_processing_spinner_.Run();
}
void ServiceWorkerPaymentApp::OnPaymentAppWindowClosed() {
delegate_ = nullptr;
content::PaymentAppProvider::GetInstance()->OnClosingOpenedWindow(
- browser_context_,
+ web_contents(),
mojom::PaymentEventResponseType::PAYMENT_HANDLER_WINDOW_CLOSING);
}
@@ -342,7 +323,7 @@ ServiceWorkerPaymentApp::CreatePaymentRequestEventData() {
}
}
- event_data->payment_handler_host = std::move(payment_handler_host_);
+ event_data->payment_handler_host = std::move(payment_handler_host_remote_);
return event_data;
}
@@ -390,7 +371,7 @@ uint32_t ServiceWorkerPaymentApp::GetCompletenessScore() const {
bool ServiceWorkerPaymentApp::CanPreselect() const {
// Do not preselect the payment app when the name and/or icon is missing.
- return !GetLabel().empty() && !icon_image_.size().IsEmpty();
+ return !GetLabel().empty() && icon_bitmap() && !icon_bitmap()->drawsNothing();
}
base::string16 ServiceWorkerPaymentApp::GetMissingInfoLabel() const {
@@ -413,6 +394,11 @@ bool ServiceWorkerPaymentApp::NeedsInstallation() const {
return needs_installation_;
}
+std::string ServiceWorkerPaymentApp::GetId() const {
+ return needs_installation_ ? installable_web_app_info_->sw_scope
+ : stored_payment_app_info_->scope.spec();
+}
+
base::string16 ServiceWorkerPaymentApp::GetLabel() const {
return base::UTF8ToUTF16(needs_installation_
? installable_web_app_info_->name
@@ -486,8 +472,40 @@ base::WeakPtr<PaymentApp> ServiceWorkerPaymentApp::AsWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
-gfx::ImageSkia ServiceWorkerPaymentApp::icon_image_skia() const {
- return icon_image_;
+const SkBitmap* ServiceWorkerPaymentApp::icon_bitmap() const {
+ return needs_installation_ ? installable_web_app_info_->icon.get()
+ : stored_payment_app_info_->icon.get();
+}
+
+std::set<std::string>
+ServiceWorkerPaymentApp::GetApplicationIdentifiersThatHideThisApp() const {
+ if (needs_installation_) {
+ return std::set<std::string>(
+ installable_web_app_info_->preferred_app_ids.begin(),
+ installable_web_app_info_->preferred_app_ids.end());
+ }
+
+ std::set<std::string> result;
+ if (!stored_payment_app_info_->prefer_related_applications)
+ return result;
+
+ for (const auto& related : stored_payment_app_info_->related_applications) {
+ result.insert(related.id);
+ }
+
+ return result;
+}
+
+bool ServiceWorkerPaymentApp::IsReadyForMinimalUI() const {
+ return is_ready_for_minimal_ui_;
+}
+
+std::string ServiceWorkerPaymentApp::GetAccountBalance() const {
+ return account_balance_;
+}
+
+void ServiceWorkerPaymentApp::DisableShowingOwnUI() {
+ can_show_own_ui_ = false;
}
bool ServiceWorkerPaymentApp::HandlesShippingAddress() const {
@@ -528,7 +546,11 @@ bool ServiceWorkerPaymentApp::HandlesPayerPhone() const {
void ServiceWorkerPaymentApp::OnPaymentAppIdentity(const url::Origin& origin,
int64_t registration_id) {
- identity_callback_.Run(origin, registration_id);
+ registration_id_ = registration_id;
+ if (payment_handler_host_) {
+ payment_handler_host_->set_sw_origin_for_logs(origin);
+ payment_handler_host_->set_registration_id_for_logs(registration_id_);
+ }
}
ukm::SourceId ServiceWorkerPaymentApp::UkmSourceId() {
@@ -546,4 +568,36 @@ ukm::SourceId ServiceWorkerPaymentApp::UkmSourceId() {
return ukm_source_id_;
}
+void ServiceWorkerPaymentApp::SetPaymentHandlerHost(
+ base::WeakPtr<PaymentHandlerHost> payment_handler_host) {
+ payment_handler_host_ = payment_handler_host;
+ payment_handler_host_remote_ = payment_handler_host_->Bind();
+}
+
+bool ServiceWorkerPaymentApp::IsWaitingForPaymentDetailsUpdate() const {
+ return payment_handler_host_ &&
+ payment_handler_host_->is_waiting_for_payment_details_update();
+}
+
+void ServiceWorkerPaymentApp::UpdateWith(
+ mojom::PaymentRequestDetailsUpdatePtr details_update) {
+ if (payment_handler_host_)
+ payment_handler_host_->UpdateWith(std::move(details_update));
+}
+
+void ServiceWorkerPaymentApp::OnPaymentDetailsNotUpdated() {
+ if (payment_handler_host_)
+ payment_handler_host_->OnPaymentDetailsNotUpdated();
+}
+
+void ServiceWorkerPaymentApp::AbortPaymentApp(
+ base::OnceCallback<void(bool)> abort_callback) {
+ content::PaymentAppProvider::GetInstance()->AbortPayment(
+ web_contents(), registration_id_,
+ stored_payment_app_info_
+ ? url::Origin::Create(stored_payment_app_info_->scope)
+ : url::Origin::Create(GURL(installable_web_app_info_->sw_scope)),
+ *spec_->details().id, std::move(abort_callback));
+}
+
} // namespace payments
diff --git a/chromium/components/payments/content/service_worker_payment_app.h b/chromium/components/payments/content/service_worker_payment_app.h
index f6e5130038f..a7ab79d38bf 100644
--- a/chromium/components/payments/content/service_worker_payment_app.h
+++ b/chromium/components/payments/content/service_worker_payment_app.h
@@ -7,19 +7,19 @@
#include <stdint.h>
-#include "base/callback_forward.h"
+#include "base/callback.h"
#include "base/memory/weak_ptr.h"
+#include "components/payments/content/payment_app.h"
#include "components/payments/content/payment_request_spec.h"
#include "components/payments/content/web_app_manifest.h"
-#include "components/payments/core/payment_app.h"
#include "content/public/browser/stored_payment_app.h"
+#include "content/public/browser/web_contents_observer.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "third_party/blink/public/mojom/payments/payment_app.mojom.h"
#include "third_party/blink/public/mojom/payments/payment_handler_host.mojom.h"
#include "third_party/blink/public/mojom/payments/payment_request.mojom.h"
namespace content {
-class BrowserContext;
class WebContents;
} // namespace content
@@ -29,24 +29,22 @@ class Origin;
namespace payments {
-class PaymentRequestDelegate;
+class PaymentHandlerHost;
// Represents a service worker based payment app.
-class ServiceWorkerPaymentApp : public PaymentApp {
+class ServiceWorkerPaymentApp : public PaymentApp,
+ public content::WebContentsObserver {
public:
- using IdentityCallback =
- base::RepeatingCallback<void(const url::Origin&, int64_t)>;
-
// This constructor is used for a payment app that has been installed in
// Chrome.
ServiceWorkerPaymentApp(
- content::BrowserContext* browser_context,
+ content::WebContents* web_contents,
const GURL& top_origin,
const GURL& frame_origin,
const PaymentRequestSpec* spec,
std::unique_ptr<content::StoredPaymentApp> stored_payment_app_info,
- PaymentRequestDelegate* payment_request_delegate,
- const IdentityCallback& identity_callback);
+ 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.
@@ -56,9 +54,9 @@ class ServiceWorkerPaymentApp : public PaymentApp {
const GURL& frame_origin,
const PaymentRequestSpec* spec,
std::unique_ptr<WebAppInstallationInfo> installable_payment_app_info,
- const std::string& enabled_methods,
- PaymentRequestDelegate* payment_request_delegate,
- const IdentityCallback& identity_callback);
+ const std::string& enabled_method,
+ bool is_incognito,
+ const base::RepeatingClosure& show_processing_spinner);
~ServiceWorkerPaymentApp() override;
// The callback for ValidateCanMakePayment.
@@ -85,6 +83,7 @@ class ServiceWorkerPaymentApp : public PaymentApp {
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;
bool IsValidForModifier(
@@ -92,17 +91,24 @@ class ServiceWorkerPaymentApp : public PaymentApp {
bool supported_networks_specified,
const std::set<std::string>& supported_networks) const override;
base::WeakPtr<PaymentApp> AsWeakPtr() override;
- gfx::ImageSkia icon_image_skia() const override;
+ const SkBitmap* icon_bitmap() const override;
+ std::set<std::string> GetApplicationIdentifiersThatHideThisApp()
+ const override;
+ bool IsReadyForMinimalUI() const override;
+ std::string GetAccountBalance() const override;
+ void DisableShowingOwnUI() override;
bool HandlesShippingAddress() const override;
bool HandlesPayerName() const override;
bool HandlesPayerEmail() const override;
bool HandlesPayerPhone() const override;
ukm::SourceId UkmSourceId() override;
-
- void set_payment_handler_host(
- mojo::PendingRemote<mojom::PaymentHandlerHost> payment_handler_host) {
- payment_handler_host_ = std::move(payment_handler_host);
- }
+ void SetPaymentHandlerHost(
+ base::WeakPtr<PaymentHandlerHost> payment_handler_host) override;
+ bool IsWaitingForPaymentDetailsUpdate() const override;
+ void UpdateWith(
+ mojom::PaymentRequestDetailsUpdatePtr details_update) override;
+ void OnPaymentDetailsNotUpdated() override;
+ void AbortPaymentApp(base::OnceCallback<void(bool)> abort_callback) override;
private:
friend class ServiceWorkerPaymentAppTest;
@@ -123,25 +129,26 @@ class ServiceWorkerPaymentApp : public PaymentApp {
// invoked.
void OnPaymentAppIdentity(const url::Origin& origin, int64_t registration_id);
- content::BrowserContext* browser_context_;
GURL top_origin_;
GURL frame_origin_;
const PaymentRequestSpec* spec_;
std::unique_ptr<content::StoredPaymentApp> stored_payment_app_info_;
- gfx::ImageSkia icon_image_;
// Weak pointer is fine here since the owner of this object is
// PaymentRequestState which also owns PaymentResponseHelper.
Delegate* delegate_;
- // Weak pointer that must outlive this object.
- PaymentRequestDelegate* payment_request_delegate_;
+ bool is_incognito_;
- // The callback that is notified of service worker registration identifier
- // after the service worker is installed.
- IdentityCallback identity_callback_;
+ // Disables user interaction by showing a spinner. Used when the app is
+ // invoked.
+ base::RepeatingClosure show_processing_spinner_;
- mojo::PendingRemote<mojom::PaymentHandlerHost> payment_handler_host_;
+ base::WeakPtr<PaymentHandlerHost> payment_handler_host_;
+ mojo::PendingRemote<mojom::PaymentHandlerHost> payment_handler_host_remote_;
+
+ // Service worker registration identifier. Used for aborting the payment app.
+ int64_t registration_id_ = 0;
// PaymentAppProvider::CanMakePayment result of this payment app.
bool can_make_payment_result_;
@@ -150,10 +157,14 @@ class ServiceWorkerPaymentApp : public PaymentApp {
// Below variables are used for installable ServiceWorkerPaymentApp
// specifically.
bool needs_installation_;
- content::WebContents* web_contents_;
std::unique_ptr<WebAppInstallationInfo> installable_web_app_info_;
std::string installable_enabled_method_;
+ // Minimal UI fields.
+ bool is_ready_for_minimal_ui_ = false;
+ std::string account_balance_;
+ bool can_show_own_ui_ = true;
+
ukm::SourceId ukm_source_id_ = ukm::kInvalidSourceId;
base::WeakPtrFactory<ServiceWorkerPaymentApp> weak_ptr_factory_{this};
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 02bb0ab41ec..5b710d33c7f 100644
--- a/chromium/components/payments/content/service_worker_payment_app_factory.cc
+++ b/chromium/components/payments/content/service_worker_payment_app_factory.cc
@@ -10,7 +10,6 @@
#include "base/bind.h"
#include "base/check_op.h"
#include "base/memory/weak_ptr.h"
-#include "components/payments/content/content_payment_request_delegate.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"
@@ -56,22 +55,15 @@ class ServiceWorkerPaymentAppCreator {
FinishAndCleanup();
return;
}
-
- if (delegate_->SkipCreatingNativePaymentApps()) {
- delegate_->OnCreatingNativePaymentAppsSkipped(
- std::move(apps), std::move(installable_apps));
- FinishAndCleanup();
- return;
- }
+ base::RepeatingClosure show_processing_spinner = base::BindRepeating(
+ &PaymentAppFactory::Delegate::ShowProcessingSpinner, delegate_);
for (auto& installed_app : apps) {
auto app = std::make_unique<ServiceWorkerPaymentApp>(
- delegate_->GetWebContents()->GetBrowserContext(),
- delegate_->GetTopOrigin(), delegate_->GetFrameOrigin(),
- delegate_->GetSpec(), std::move(installed_app.second),
- delegate_->GetPaymentRequestDelegate(),
- base::BindRepeating(
- &PaymentAppFactory::Delegate::OnPaymentAppInstalled, delegate_));
+ delegate_->GetWebContents(), delegate_->GetTopOrigin(),
+ delegate_->GetFrameOrigin(), delegate_->GetSpec(),
+ std::move(installed_app.second), delegate_->IsOffTheRecord(),
+ show_processing_spinner);
app->ValidateCanMakePayment(base::BindOnce(
&ServiceWorkerPaymentAppCreator::OnSWPaymentAppValidated,
weak_ptr_factory_.GetWeakPtr()));
@@ -84,9 +76,7 @@ class ServiceWorkerPaymentAppCreator {
delegate_->GetWebContents(), delegate_->GetTopOrigin(),
delegate_->GetFrameOrigin(), delegate_->GetSpec(),
std::move(installable_app.second), installable_app.first.spec(),
- delegate_->GetPaymentRequestDelegate(),
- base::BindRepeating(
- &PaymentAppFactory::Delegate::OnPaymentAppInstalled, delegate_));
+ delegate_->IsOffTheRecord(), show_processing_spinner);
app->ValidateCanMakePayment(base::BindOnce(
&ServiceWorkerPaymentAppCreator::OnSWPaymentAppValidated,
weak_ptr_factory_.GetWeakPtr()));
@@ -145,18 +135,19 @@ void ServiceWorkerPaymentAppFactory::Create(base::WeakPtr<Delegate> delegate) {
ServiceWorkerPaymentAppCreator* creator_raw_pointer = creator.get();
creators_[creator_raw_pointer] = std::move(creator);
- ServiceWorkerPaymentAppFinder::GetInstance()->GetAllPaymentApps(
- delegate->GetFrameSecurityOrigin(),
- delegate->GetInitiatorRenderFrameHost(), delegate->GetWebContents(),
- delegate->GetPaymentManifestWebDataService(),
- Clone(delegate->GetMethodData()),
- delegate->MayCrawlForInstallablePaymentApps(),
- base::BindOnce(&ServiceWorkerPaymentAppCreator::CreatePaymentApps,
- creator_raw_pointer->GetWeakPtr()),
- base::BindOnce([]() {
- // Nothing needs to be done after writing cache. This callback is used
- // only in tests.
- }));
+ ServiceWorkerPaymentAppFinder::GetOrCreateForCurrentDocument(
+ delegate->GetInitiatorRenderFrameHost())
+ ->GetAllPaymentApps(
+ delegate->GetFrameSecurityOrigin(),
+ delegate->GetPaymentManifestWebDataService(),
+ Clone(delegate->GetMethodData()),
+ delegate->MayCrawlForInstallablePaymentApps(),
+ base::BindOnce(&ServiceWorkerPaymentAppCreator::CreatePaymentApps,
+ creator_raw_pointer->GetWeakPtr()),
+ base::BindOnce([]() {
+ // Nothing needs to be done after writing cache. This callback is
+ // used only in tests.
+ }));
}
void ServiceWorkerPaymentAppFactory::DeleteCreator(
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 4fa55d89eae..87b33d0f5af 100644
--- a/chromium/components/payments/content/service_worker_payment_app_finder.cc
+++ b/chromium/components/payments/content/service_worker_payment_app_finder.cc
@@ -7,12 +7,13 @@
#include <algorithm>
#include <vector>
+#include "base/base64.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/check.h"
#include "base/memory/ref_counted.h"
-#include "base/memory/singleton.h"
#include "base/stl_util.h"
+#include "base/supports_user_data.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/payments/content/developer_console_logger.h"
#include "components/payments/content/installable_payment_app_crawler.h"
@@ -22,13 +23,16 @@
#include "components/payments/core/features.h"
#include "components/payments/core/method_strings.h"
#include "components/payments/core/payment_manifest_downloader.h"
+#include "components/payments/core/url_util.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/payment_app_provider.h"
#include "content/public/browser/render_frame_host.h"
#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"
namespace payments {
@@ -88,10 +92,28 @@ void RemovePortNumbersFromScopesForTest(
}
}
-class SelfDeletingServiceWorkerPaymentAppFinder {
+class SelfDeletingServiceWorkerPaymentAppFinder
+ : public base::SupportsUserData::Data {
public:
- SelfDeletingServiceWorkerPaymentAppFinder() {}
- ~SelfDeletingServiceWorkerPaymentAppFinder() {}
+ static base::WeakPtr<SelfDeletingServiceWorkerPaymentAppFinder>
+ CreateAndSetOwnedBy(base::SupportsUserData* owner) {
+ auto owned =
+ std::make_unique<SelfDeletingServiceWorkerPaymentAppFinder>(owner);
+ auto* pointer = owned.get();
+ owner->SetUserData(pointer, std::move(owned));
+ return pointer->weak_ptr_factory_.GetWeakPtr();
+ }
+
+ explicit SelfDeletingServiceWorkerPaymentAppFinder(
+ base::SupportsUserData* owner)
+ : owner_(owner) {}
+
+ SelfDeletingServiceWorkerPaymentAppFinder(
+ const SelfDeletingServiceWorkerPaymentAppFinder& other) = delete;
+ SelfDeletingServiceWorkerPaymentAppFinder& operator=(
+ const SelfDeletingServiceWorkerPaymentAppFinder& other) = delete;
+
+ ~SelfDeletingServiceWorkerPaymentAppFinder() override = default;
// After |callback| has fired, the factory refreshes its own cache in the
// background. Once the cache has been refreshed, the factory invokes the
@@ -101,7 +123,6 @@ class SelfDeletingServiceWorkerPaymentAppFinder {
void GetAllPaymentApps(
const url::Origin& merchant_origin,
content::RenderFrameHost* initiator_render_frame_host,
- content::WebContents* web_contents,
std::unique_ptr<PaymentManifestDownloader> downloader,
scoped_refptr<PaymentManifestWebDataService> cache,
const std::vector<mojom::PaymentMethodDataPtr>& requested_method_data,
@@ -111,6 +132,8 @@ class SelfDeletingServiceWorkerPaymentAppFinder {
DCHECK(!verifier_);
downloader_ = std::move(downloader);
+ content::WebContents* web_contents =
+ content::WebContents::FromRenderFrameHost(initiator_render_frame_host);
parser_ = std::make_unique<PaymentManifestParser>(
std::make_unique<DeveloperConsoleLogger>(web_contents));
cache_ = cache;
@@ -149,6 +172,11 @@ class SelfDeletingServiceWorkerPaymentAppFinder {
}
private:
+ // base::SupportsUserData::Data implementation.
+ std::unique_ptr<Data> Clone() override {
+ return nullptr; // Cloning is not supported.
+ }
+
static void RemoveUnrequestedMethods(
const std::vector<mojom::PaymentMethodDataPtr>& requested_method_data,
content::PaymentAppProvider::PaymentApps* apps) {
@@ -196,12 +224,35 @@ class SelfDeletingServiceWorkerPaymentAppFinder {
const std::string& error_message) {
if (first_error_message_.empty())
first_error_message_ = error_message;
- if (apps.empty() && crawler_ != nullptr) {
+
+ std::set<GURL> method_manifest_urls_for_icon_refetch;
+ installed_apps_ = std::move(apps);
+ for (auto& app : installed_apps_) {
+ if (app.second->icon.get() && !app.second->icon.get()->drawsNothing()) {
+ continue;
+ }
+
+ for (const auto& method : app.second->enabled_methods) {
+ // Only payment methods with manifests are eligible for refetching the
+ // icon of their installed payment apps.
+ GURL method_manifest_url = GURL(method);
+ if (!UrlUtil::IsValidUrlBasedPaymentMethodIdentifier(
+ method_manifest_url)) {
+ continue;
+ }
+ method_manifest_urls_for_icon_refetch.insert(method_manifest_url);
+ }
+ }
+
+ if ((installed_apps_.empty() ||
+ !method_manifest_urls_for_icon_refetch.empty()) &&
+ crawler_ != nullptr) {
// Crawls installable web payment apps if no web payment apps have been
- // installed.
+ // installed or when an installed app is missing an icon.
is_payment_app_crawler_finished_using_resources_ = false;
crawler_->Start(
requested_method_data_,
+ std::move(method_manifest_urls_for_icon_refetch),
base::BindOnce(
&SelfDeletingServiceWorkerPaymentAppFinder::OnPaymentAppsCrawled,
weak_ptr_factory_.GetWeakPtr()),
@@ -215,18 +266,68 @@ class SelfDeletingServiceWorkerPaymentAppFinder {
crawler_.reset();
std::move(callback_).Run(
- std::move(apps),
+ std::move(installed_apps_),
ServiceWorkerPaymentAppFinder::InstallablePaymentApps(),
first_error_message_);
}
void OnPaymentAppsCrawled(
std::map<GURL, std::unique_ptr<WebAppInstallationInfo>> apps_info,
+ std::map<GURL, std::unique_ptr<RefetchedIcon>> refetched_icons,
const std::string& error_message) {
if (first_error_message_.empty())
first_error_message_ = error_message;
- std::move(callback_).Run(content::PaymentAppProvider::PaymentApps(),
- std::move(apps_info), first_error_message_);
+
+ for (auto& refetched_icon : refetched_icons) {
+ GURL web_app_manifest_url = refetched_icon.first;
+ RefetchedIcon* data = refetched_icon.second.get();
+ for (auto& app : installed_apps_) {
+ // It is possible (unlikely) to have multiple apps with same origins.
+ // The proper validation is to store web_app_manifest_url in
+ // StoredPaymentApp and confirm that it is the same as the
+ // web_app_manifest_url from which icon is fetched.
+ if (crawler_->IsSameOriginWith(GURL(app.second->scope),
+ web_app_manifest_url)) {
+ UpdatePaymentAppIcon(app.second, data->icon, data->method_name);
+ app.second->icon = std::move(data->icon);
+ break;
+ }
+ }
+ }
+ std::move(callback_).Run(std::move(installed_apps_), std::move(apps_info),
+ first_error_message_);
+ }
+
+ void UpdatePaymentAppIcon(
+ const std::unique_ptr<content::StoredPaymentApp>& app,
+ const std::unique_ptr<SkBitmap>& icon,
+ const std::string& method_name) {
+ number_of_app_icons_to_update_++;
+
+ DCHECK(!icon->empty());
+ std::string string_encoded_icon;
+ gfx::Image decoded_image = gfx::Image::CreateFrom1xBitmap(*(icon));
+ scoped_refptr<base::RefCountedMemory> raw_data =
+ decoded_image.As1xPNGBytes();
+ base::Base64Encode(
+ base::StringPiece(raw_data->front_as<char>(), raw_data->size()),
+ &string_encoded_icon);
+
+ auto* browser_context =
+ static_cast<content::WebContents*>(owner_)->GetBrowserContext();
+ content::PaymentAppProvider::GetInstance()->UpdatePaymentAppIcon(
+ browser_context, app->registration_id, app->scope.spec(), app->name,
+ string_encoded_icon, method_name, app->supported_delegations,
+ base::BindOnce(
+ &SelfDeletingServiceWorkerPaymentAppFinder::OnUpdatePaymentAppIcon,
+ weak_ptr_factory_.GetWeakPtr()));
+ }
+
+ void OnUpdatePaymentAppIcon(payments::mojom::PaymentHandlerStatus status) {
+ DCHECK(number_of_app_icons_to_update_ > 0);
+ number_of_app_icons_to_update_--;
+ if (number_of_app_icons_to_update_ == 0)
+ FinishUsingResourcesIfReady();
}
void OnPaymentAppsCrawlerFinishedUsingResources() {
@@ -246,15 +347,25 @@ class SelfDeletingServiceWorkerPaymentAppFinder {
void FinishUsingResourcesIfReady() {
if (is_payment_verifier_finished_using_resources_ &&
is_payment_app_crawler_finished_using_resources_ &&
- !finished_using_resources_callback_.is_null()) {
+ !finished_using_resources_callback_.is_null() &&
+ number_of_app_icons_to_update_ == 0) {
downloader_.reset();
parser_.reset();
std::move(finished_using_resources_callback_).Run();
- base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, this);
+ base::ThreadTaskRunnerHandle::Get()->PostNonNestableTask(
+ FROM_HERE,
+ base::BindOnce(&SelfDeletingServiceWorkerPaymentAppFinder::DeleteSelf,
+ weak_ptr_factory_.GetWeakPtr()));
}
}
+ void DeleteSelf() { owner_->RemoveUserData(this); }
+
+ // |owner_| owns this SelfDeletingServiceWorkerPaymentAppFinder, so it is
+ // always valid.
+ base::SupportsUserData* owner_;
+
std::unique_ptr<PaymentManifestDownloader> downloader_;
std::unique_ptr<PaymentManifestParser> parser_;
scoped_refptr<PaymentManifestWebDataService> cache_;
@@ -271,23 +382,20 @@ class SelfDeletingServiceWorkerPaymentAppFinder {
bool ignore_port_in_origin_comparison_for_testing_ = false;
+ content::PaymentAppProvider::PaymentApps installed_apps_;
+
+ size_t number_of_app_icons_to_update_ = 0;
+
base::WeakPtrFactory<SelfDeletingServiceWorkerPaymentAppFinder>
weak_ptr_factory_{this};
-
- DISALLOW_COPY_AND_ASSIGN(SelfDeletingServiceWorkerPaymentAppFinder);
};
} // namespace
-// static
-ServiceWorkerPaymentAppFinder* ServiceWorkerPaymentAppFinder::GetInstance() {
- return base::Singleton<ServiceWorkerPaymentAppFinder>::get();
-}
+ServiceWorkerPaymentAppFinder::~ServiceWorkerPaymentAppFinder() = default;
void ServiceWorkerPaymentAppFinder::GetAllPaymentApps(
const url::Origin& merchant_origin,
- content::RenderFrameHost* initiator_render_frame_host,
- content::WebContents* web_contents,
scoped_refptr<PaymentManifestWebDataService> cache,
std::vector<mojom::PaymentMethodDataPtr> requested_method_data,
bool may_crawl_for_installable_payment_apps,
@@ -308,8 +416,12 @@ void ServiceWorkerPaymentAppFinder::GetAllPaymentApps(
return;
}
- SelfDeletingServiceWorkerPaymentAppFinder* self_delete_factory =
- new SelfDeletingServiceWorkerPaymentAppFinder();
+ content::WebContents* web_contents =
+ content::WebContents::FromRenderFrameHost(rfh_);
+ auto self_delete_factory =
+ SelfDeletingServiceWorkerPaymentAppFinder::CreateAndSetOwnedBy(
+ web_contents);
+
std::unique_ptr<PaymentManifestDownloader> downloader;
if (test_downloader_ != nullptr) {
downloader = std::move(test_downloader_);
@@ -323,9 +435,9 @@ void ServiceWorkerPaymentAppFinder::GetAllPaymentApps(
}
self_delete_factory->GetAllPaymentApps(
- merchant_origin, initiator_render_frame_host, web_contents,
- std::move(downloader), cache, requested_method_data,
- may_crawl_for_installable_payment_apps, std::move(callback),
+ merchant_origin, rfh_, std::move(downloader), cache,
+ requested_method_data, may_crawl_for_installable_payment_apps,
+ std::move(callback),
std::move(finished_writing_cache_callback_for_testing));
}
@@ -348,16 +460,18 @@ void ServiceWorkerPaymentAppFinder::IgnorePaymentMethodForTest(
ignored_methods_.insert(method);
}
-ServiceWorkerPaymentAppFinder::ServiceWorkerPaymentAppFinder()
- : ignored_methods_({methods::kGooglePlayBilling}),
+ServiceWorkerPaymentAppFinder::ServiceWorkerPaymentAppFinder(
+ content::RenderFrameHost* rfh)
+ : rfh_(rfh),
+ ignored_methods_({methods::kGooglePlayBilling}),
test_downloader_(nullptr) {}
-ServiceWorkerPaymentAppFinder::~ServiceWorkerPaymentAppFinder() = default;
-
void ServiceWorkerPaymentAppFinder::
SetDownloaderAndIgnorePortInOriginComparisonForTesting(
std::unique_ptr<PaymentManifestDownloader> downloader) {
test_downloader_ = std::move(downloader);
}
+RENDER_DOCUMENT_HOST_USER_DATA_KEY_IMPL(ServiceWorkerPaymentAppFinder)
+
} // namespace payments
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 09e2c51197e..980e4f044ed 100644
--- a/chromium/components/payments/content/service_worker_payment_app_finder.h
+++ b/chromium/components/payments/content/service_worker_payment_app_finder.h
@@ -14,6 +14,7 @@
#include "base/macros.h"
#include "components/payments/content/web_app_manifest.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"
class GURL;
@@ -21,14 +22,8 @@ class GURL;
template <class T>
class scoped_refptr;
-namespace base {
-template <typename Type>
-struct DefaultSingletonTraits;
-} // namespace base
-
namespace content {
class RenderFrameHost;
-class WebContents;
} // namespace content
namespace url {
@@ -41,7 +36,9 @@ class PaymentManifestDownloader;
class PaymentManifestWebDataService;
// Retrieves service worker payment apps.
-class ServiceWorkerPaymentAppFinder {
+class ServiceWorkerPaymentAppFinder
+ : public content::RenderDocumentHostUserData<
+ ServiceWorkerPaymentAppFinder> {
public:
using InstallablePaymentApps =
std::map<GURL, std::unique_ptr<WebAppInstallationInfo>>;
@@ -50,7 +47,7 @@ class ServiceWorkerPaymentAppFinder {
InstallablePaymentApps,
const std::string& error_message)>;
- static ServiceWorkerPaymentAppFinder* GetInstance();
+ ~ServiceWorkerPaymentAppFinder() override;
// Retrieves all service worker payment apps that can handle payments for
// |requested_method_data|, verifies these apps are allowed to handle these
@@ -71,8 +68,6 @@ class ServiceWorkerPaymentAppFinder {
// The method should be called on the UI thread.
void GetAllPaymentApps(
const url::Origin& merchant_origin,
- content::RenderFrameHost* initiator_render_frame_host,
- content::WebContents* web_contents,
scoped_refptr<PaymentManifestWebDataService> cache,
std::vector<mojom::PaymentMethodDataPtr> requested_method_data,
bool may_crawl_for_installable_payment_apps,
@@ -91,14 +86,15 @@ class ServiceWorkerPaymentAppFinder {
void IgnorePaymentMethodForTest(const std::string& method);
private:
- friend struct base::DefaultSingletonTraits<ServiceWorkerPaymentAppFinder>;
+ friend class content::RenderDocumentHostUserData<
+ ServiceWorkerPaymentAppFinder>;
+ friend class IframeCspTest;
friend class PaymentRequestPaymentAppTest;
friend class ServiceWorkerPaymentAppFinderBrowserTest;
friend class PaymentRequestPlatformBrowserTestBase;
friend class PaymentMethodViewControllerTest;
- ServiceWorkerPaymentAppFinder();
- ~ServiceWorkerPaymentAppFinder();
+ explicit ServiceWorkerPaymentAppFinder(content::RenderFrameHost* rfh);
// Should be used only in tests.
// Should be called before every call to GetAllPaymentApps() (because the test
@@ -106,6 +102,11 @@ class ServiceWorkerPaymentAppFinder {
void SetDownloaderAndIgnorePortInOriginComparisonForTesting(
std::unique_ptr<PaymentManifestDownloader> downloader);
+ RENDER_DOCUMENT_HOST_USER_DATA_KEY_DECL();
+
+ // |rfh_| owns this ServiceWorkerPaymentAppFinder, so it is always valid.
+ content::RenderFrameHost* rfh_;
+
std::set<std::string> ignored_methods_;
std::unique_ptr<PaymentManifestDownloader> test_downloader_;
diff --git a/chromium/components/payments/content/service_worker_payment_app_unittest.cc b/chromium/components/payments/content/service_worker_payment_app_unittest.cc
index 3128121c1ab..70641155431 100644
--- a/chromium/components/payments/content/service_worker_payment_app_unittest.cc
+++ b/chromium/components/payments/content/service_worker_payment_app_unittest.cc
@@ -8,10 +8,10 @@
#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
-#include "components/payments/core/mock_payment_request_delegate.h"
#include "content/public/browser/stored_payment_app.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/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/payments/payment_request.mojom.h"
@@ -20,8 +20,11 @@ namespace payments {
class ServiceWorkerPaymentAppTest : public testing::Test,
public PaymentRequestSpec::Observer {
public:
- ServiceWorkerPaymentAppTest() {}
- ~ServiceWorkerPaymentAppTest() override {}
+ ServiceWorkerPaymentAppTest() {
+ web_contents_ =
+ test_web_contents_factory_.CreateWebContents(&browser_context_);
+ }
+ ~ServiceWorkerPaymentAppTest() override = default;
protected:
const SkBitmap* icon_bitmap() const { return icon_bitmap_; }
@@ -113,12 +116,10 @@ class ServiceWorkerPaymentAppTest : public testing::Test,
icon_bitmap_ = stored_app->icon.get();
app_ = std::make_unique<ServiceWorkerPaymentApp>(
- &browser_context_, GURL("https://testmerchant.com"),
+ web_contents_, GURL("https://testmerchant.com"),
GURL("https://testmerchant.com/bobpay"), spec_.get(),
- std::move(stored_app), &delegate_,
- base::BindRepeating(
- [](const url::Origin& origin,
- int64_t registration_id) { /* Intentionally left blank. */ }));
+ std::move(stored_app), /*is_incognito=*/false,
+ /*show_processing_spinner=*/base::DoNothing());
}
ServiceWorkerPaymentApp* GetApp() { return app_.get(); }
@@ -132,9 +133,10 @@ class ServiceWorkerPaymentAppTest : public testing::Test,
}
private:
- MockPaymentRequestDelegate delegate_;
content::BrowserTaskEnvironment task_environment_;
content::TestBrowserContext browser_context_;
+ content::TestWebContentsFactory test_web_contents_factory_;
+ content::WebContents* web_contents_;
std::unique_ptr<PaymentRequestSpec> spec_;
std::unique_ptr<ServiceWorkerPaymentApp> app_;
@@ -152,9 +154,9 @@ TEST_F(ServiceWorkerPaymentAppTest, AppInfo) {
EXPECT_EQ(base::UTF16ToUTF8(GetApp()->GetLabel()), "bobpay");
EXPECT_EQ(base::UTF16ToUTF8(GetApp()->GetSublabel()), "bobpay.com");
- const gfx::Size expected_size{icon_bitmap()->width(),
- icon_bitmap()->height()};
- EXPECT_EQ(GetApp()->icon_image_skia().size(), expected_size);
+ ASSERT_NE(nullptr, GetApp()->icon_bitmap());
+ EXPECT_EQ(GetApp()->icon_bitmap()->width(), icon_bitmap()->width());
+ EXPECT_EQ(GetApp()->icon_bitmap()->height(), icon_bitmap()->height());
}
// Test payment request event data can be correctly constructed for invoking
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 343f29e37ea..281f95c2a40 100644
--- a/chromium/components/payments/content/test_content_payment_request_delegate.cc
+++ b/chromium/components/payments/content/test_content_payment_request_delegate.cc
@@ -53,6 +53,10 @@ bool TestContentPaymentRequestDelegate::SkipUiForBasicCard() const {
return false;
}
+std::string TestContentPaymentRequestDelegate::GetTwaPackageName() const {
+ return "";
+}
+
autofill::PersonalDataManager*
TestContentPaymentRequestDelegate::GetPersonalDataManager() {
return core_delegate_.GetPersonalDataManager();
@@ -63,8 +67,8 @@ const std::string& TestContentPaymentRequestDelegate::GetApplicationLocale()
return core_delegate_.GetApplicationLocale();
}
-bool TestContentPaymentRequestDelegate::IsIncognito() const {
- return core_delegate_.IsIncognito();
+bool TestContentPaymentRequestDelegate::IsOffTheRecord() const {
+ return core_delegate_.IsOffTheRecord();
}
const GURL& TestContentPaymentRequestDelegate::GetLastCommittedURL() const {
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 f7125099137..4cf7dceda3f 100644
--- a/chromium/components/payments/content/test_content_payment_request_delegate.h
+++ b/chromium/components/payments/content/test_content_payment_request_delegate.h
@@ -32,9 +32,10 @@ class TestContentPaymentRequestDelegate : public ContentPaymentRequestDelegate {
void ShowProcessingSpinner() override;
bool IsBrowserWindowActive() const override;
bool SkipUiForBasicCard() const override;
+ std::string GetTwaPackageName() const override;
autofill::PersonalDataManager* GetPersonalDataManager() override;
const std::string& GetApplicationLocale() const override;
- bool IsIncognito() const override;
+ bool IsOffTheRecord() const override;
const GURL& GetLastCommittedURL() const override;
void DoFullCardRequest(
const autofill::CreditCard& credit_card,