diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-07-16 11:45:35 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-07-17 08:59:23 +0000 |
commit | 552906b0f222c5d5dd11b9fd73829d510980461a (patch) | |
tree | 3a11e6ed0538a81dd83b20cf3a4783e297f26d91 /chromium/components/payments | |
parent | 1b05827804eaf047779b597718c03e7d38344261 (diff) | |
download | qtwebengine-chromium-552906b0f222c5d5dd11b9fd73829d510980461a.tar.gz |
BASELINE: Update Chromium to 83.0.4103.122
Change-Id: Ie3a82f5bb0076eec2a7c6a6162326b4301ee291e
Reviewed-by: Michael Brüning <michael.bruning@qt.io>
Diffstat (limited to 'chromium/components/payments')
90 files changed, 2726 insertions, 1285 deletions
diff --git a/chromium/components/payments/OWNERS b/chromium/components/payments/OWNERS index 35f24cd7f6f..05079eaab8e 100644 --- a/chromium/components/payments/OWNERS +++ b/chromium/components/payments/OWNERS @@ -1,7 +1,10 @@ -gogerald@chromium.org +# TEAM: payments-dev@chromium.org +# COMPONENT: UI>Browser>Payments + rouslan@chromium.org danyao@chromium.org maxlg@chromium.org sahel@chromium.org -# COMPONENT: UI>Browser>Payments +# Emeritus +gogerald@chromium.org diff --git a/chromium/components/payments/content/OWNERS b/chromium/components/payments/content/OWNERS deleted file mode 100644 index b315c002aac..00000000000 --- a/chromium/components/payments/content/OWNERS +++ /dev/null @@ -1,7 +0,0 @@ -per-file payment_response_helper*=sebsg@chromium.org - -per-file payment_manifest_web_data_service*=gogerald@chromium.org -per-file payment_method_manifest_table*=gogerald@chromium.org -per-file web_app_manifest_section_table*=gogerald@chromium.org - -# COMPONENT: UI>Browser>Payments diff --git a/chromium/components/payments/content/android/BUILD.gn b/chromium/components/payments/content/android/BUILD.gn index 3ec052c341c..b7e430a2eec 100644 --- a/chromium/components/payments/content/android/BUILD.gn +++ b/chromium/components/payments/content/android/BUILD.gn @@ -12,6 +12,7 @@ static_library("android") { "byte_buffer_helper.h", "currency_formatter_android.cc", "currency_formatter_android.h", + "error_message_util.cc", "origin_security_checker_android.cc", "payment_handler_host.cc", "payment_handler_host.h", @@ -32,12 +33,15 @@ static_library("android") { "//components/payments/core", "//content/public/browser", "//net", + "//url:gurl_android", + "//url:origin_android", ] } 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/OriginSecurityChecker.java", "java/src/org/chromium/components/payments/PaymentHandlerHost.java", "java/src/org/chromium/components/payments/PaymentManifestDownloader.java", @@ -49,16 +53,22 @@ generate_jni("jni_headers") { android_library("java") { annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ] - java_files = [ + sources = [ + "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/OriginSecurityChecker.java", + "java/src/org/chromium/components/payments/PayerData.java", "java/src/org/chromium/components/payments/PaymentDetailsConverter.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/PaymentValidator.java", - "java/src/org/chromium/components/payments/WebAppManifestSection.java", "java/src/org/chromium/components/payments/UrlUtil.java", + "java/src/org/chromium/components/payments/WebAppManifestSection.java", + "java/src/org/chromium/components/payments/intent/IsReadyToPayServiceHelper.java", + "java/src/org/chromium/components/payments/intent/WebPaymentIntentHelper.java", + "java/src/org/chromium/components/payments/intent/WebPaymentIntentHelperType.java", ] deps = [ "//base:base_java", @@ -67,21 +77,20 @@ android_library("java") { "//content/public/android:content_java", "//mojo/public/java:bindings_java", "//third_party/blink/public/mojom:android_mojo_bindings_java", + "//url:gurl_java", + "//url:origin_java", ] + srcjar_deps = [ ":error_strings_generated_srcjar" ] } java_cpp_strings("error_strings_generated_srcjar") { - sources = [ - "//components/payments/core/error_strings.cc", - ] + sources = [ "//components/payments/core/error_strings.cc" ] template = "java_templates/ErrorStrings.java.tmpl" } java_cpp_strings("method_strings_generated_srcjar") { - sources = [ - "//components/payments/core/method_strings.cc", - ] + sources = [ "//components/payments/core/method_strings.cc" ] template = "java_templates/MethodStrings.java.tmpl" } diff --git a/chromium/components/payments/content/android/OWNERS b/chromium/components/payments/content/android/OWNERS deleted file mode 100644 index 55cc03e0e05..00000000000 --- a/chromium/components/payments/content/android/OWNERS +++ /dev/null @@ -1,3 +0,0 @@ -gogerald@chromium.org - -# COMPONENT: UI>Browser>Payments diff --git a/chromium/components/payments/content/android/byte_buffer_helper.cc b/chromium/components/payments/content/android/byte_buffer_helper.cc index d95555719d3..bef82fc4a39 100644 --- a/chromium/components/payments/content/android/byte_buffer_helper.cc +++ b/chromium/components/payments/content/android/byte_buffer_helper.cc @@ -13,9 +13,10 @@ namespace android { std::vector<uint8_t> JavaByteBufferToNativeByteVector( JNIEnv* env, - const base::android::JavaParamRef<jobject>& buffer) { - jbyte* buf_in = static_cast<jbyte*>(env->GetDirectBufferAddress(buffer)); - jlong buf_size = env->GetDirectBufferCapacity(buffer); + const base::android::JavaRef<jobject>& buffer) { + jbyte* buf_in = + static_cast<jbyte*>(env->GetDirectBufferAddress(buffer.obj())); + jlong buf_size = env->GetDirectBufferCapacity(buffer.obj()); std::vector<uint8_t> result(buf_size); memcpy(&result[0], buf_in, buf_size); return result; diff --git a/chromium/components/payments/content/android/byte_buffer_helper.h b/chromium/components/payments/content/android/byte_buffer_helper.h index b72880583e1..fd21d4a2b93 100644 --- a/chromium/components/payments/content/android/byte_buffer_helper.h +++ b/chromium/components/payments/content/android/byte_buffer_helper.h @@ -10,6 +10,8 @@ #include <vector> #include "base/android/scoped_java_ref.h" +#include "base/logging.h" +#include "mojo/public/cpp/bindings/struct_ptr.h" namespace payments { namespace android { @@ -22,7 +24,39 @@ namespace android { // &details); std::vector<uint8_t> JavaByteBufferToNativeByteVector( JNIEnv* env, - const base::android::JavaParamRef<jobject>& buffer); + const base::android::JavaRef<jobject>& buffer); + +// Deserializes a java.nio.ByteBuffer into a native Mojo object. Returns true if +// deserialization is successful. +template <typename T> +bool DeserializeFromJavaByteBuffer( + JNIEnv* env, + const base::android::JavaRef<jobject>& jbuffer, + mojo::StructPtr<T>* out) { + DCHECK(out); + return T::Deserialize(JavaByteBufferToNativeByteVector(env, jbuffer), out); +} + +// Deserializes a java.nio.ByteBuffer[] into a vector of native Mojo objects. +// The content of |out| is replaced. Returns true if all entries are +// deserialized successfully. +template <typename T> +bool DeserializeFromJavaByteBufferArray( + JNIEnv* env, + const base::android::JavaRef<jobjectArray>& jbuffers, + std::vector<mojo::StructPtr<T>>* out) { + DCHECK(out); + out->clear(); + for (const auto& jbuffer : jbuffers.ReadElements<jobject>()) { + mojo::StructPtr<T> data; + if (!DeserializeFromJavaByteBuffer(env, jbuffer, &data)) { + out->clear(); + return false; + } + out->push_back(std::move(data)); + } + return true; +} } // namespace android } // namespace payments diff --git a/chromium/components/payments/content/android/error_message_util.cc b/chromium/components/payments/content/android/error_message_util.cc new file mode 100644 index 00000000000..8a3abc3253f --- /dev/null +++ b/chromium/components/payments/content/android/error_message_util.cc @@ -0,0 +1,33 @@ +// 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 <jni.h> +#include <set> +#include <string> +#include <vector> + +#include "base/android/jni_array.h" +#include "base/android/jni_string.h" +#include "base/android/scoped_java_ref.h" +#include "components/payments/content/android/jni_headers/ErrorMessageUtil_jni.h" +#include "components/payments/core/error_message_util.h" + +namespace payments { +namespace android { + +// static +base::android::ScopedJavaLocalRef<jstring> +JNI_ErrorMessageUtil_GetNotSupportedErrorMessage( + JNIEnv* env, + const base::android::JavaParamRef<jobjectArray>& jmethods) { + std::vector<std::string> method_vector; + base::android::AppendJavaStringArrayToStringVector(env, jmethods, + &method_vector); + return base::android::ConvertUTF8ToJavaString( + env, GetNotSupportedErrorMessage(std::set<std::string>( + method_vector.begin(), method_vector.end()))); +} + +} // namespace android +} // 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 new file mode 100644 index 00000000000..6a87e1ca378 --- /dev/null +++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/Address.java @@ -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. + +package org.chromium.components.payments; + +/** + * An immutable class that mirrors org.chromium.payments.mojom.PaymentAddress. + * https://w3c.github.io/payment-request/#paymentaddress-interface + */ +public class Address { + public final String country; + public final String[] addressLine; + public final String region; + public final String city; + public final String dependentLocality; + public final String postalCode; + public final String sortingCode; + public final String organization; + public final String recipient; + public final String phone; + + public Address() { + country = ""; + addressLine = new String[0]; + region = ""; + city = ""; + dependentLocality = ""; + postalCode = ""; + sortingCode = ""; + organization = ""; + recipient = ""; + phone = ""; + } + + /** + * @param country The country corresponding to the address. + * @param addressLine The most specific part of the address. It can include, for example, a + * street name, a house number, apartment number, a rural delivery route, descriptive + * instructions, or a post office box number. + * @param region The top level administrative subdivision of the country. For example, this can + * be a state, a province, an oblast, or a prefecture. + * @param city The city/town portion of the address. + * @param dependentLocalitly The dependent locality or sublocality within a city. For example, + * neighborhoods, boroughs, districts, or UK dependent localities. + * @param postalCode The postal code or ZIP code, also known as PIN code in India. + * @param sortingCode The sorting code as used in, for example, France. + * @param organization The organization, firm, company, or institution at the address. + * @param recipient The name of the recipient or contact person at the address. + * @param phone The phone number of the recipient or contact person at the address. + */ + public Address(String country, String[] addressLine, String region, String city, + String dependentLocality, String postalCode, String sortingCode, String organization, + String recipient, String phone) { + this.country = country; + this.addressLine = addressLine; + this.region = region; + this.city = city; + this.dependentLocality = dependentLocality; + this.postalCode = postalCode; + this.sortingCode = sortingCode; + this.organization = organization; + this.recipient = recipient; + this.phone = phone; + } +} diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/ErrorMessageUtil.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/ErrorMessageUtil.java new file mode 100644 index 00000000000..dd97c84d32f --- /dev/null +++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/ErrorMessageUtil.java @@ -0,0 +1,33 @@ +// 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.JNINamespace; +import org.chromium.base.annotations.NativeMethods; + +import java.util.Set; + +/** Error messages for web payment. */ +@JNINamespace("payments::android") +public class ErrorMessageUtil { + /** + * Returns the "payment method not supported" message. + * @param methods The payment methods that are not supported. + * @return The web-developer facing error message. + */ + public static String getNotSupportedErrorMessage(Set<String> methods) { + return ErrorMessageUtilJni.get().getNotSupportedErrorMessage( + methods.toArray(new String[methods.size()])); + } + + /** + * The interface implemented by the automatically generated JNI bindings class + * ErrorMessageUtilJni. + */ + @NativeMethods + /* package */ interface Natives { + String getNotSupportedErrorMessage(String[] methods); + } +} diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PayerData.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PayerData.java new file mode 100644 index 00000000000..5dc1732822a --- /dev/null +++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PayerData.java @@ -0,0 +1,40 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.components.payments; + +/** + * An immutable class used to bundle the payer data received from payment handlers. + */ +public class PayerData { + public final String payerName; + public final String payerPhone; + public final String payerEmail; + public final Address shippingAddress; + public final String selectedShippingOptionId; + + /** + * @param payerName The payer's name. + * @param payerPhone The payer's phone number. + * @param payerEmail The payer's email address. + * @param shippingAddress The user selected shippingAddress. + * @param selectedShippingOptionId The user selected shipping option's identifier. + */ + public PayerData(String payerName, String payerPhone, String payerEmail, + Address shippingAddress, String selectedShippingOptionId) { + this.payerName = payerName; + this.payerPhone = payerPhone; + this.payerEmail = payerEmail; + this.shippingAddress = shippingAddress; + this.selectedShippingOptionId = selectedShippingOptionId; + } + + public PayerData() { + payerName = null; + payerPhone = null; + payerEmail = null; + shippingAddress = null; + selectedShippingOptionId = null; + } +} diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentManifestDownloader.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentManifestDownloader.java index f53e3d46ccd..681db5652fd 100644 --- a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentManifestDownloader.java +++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentManifestDownloader.java @@ -4,13 +4,16 @@ package org.chromium.components.payments; +import androidx.annotation.VisibleForTesting; + import org.chromium.base.ThreadUtils; 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 java.net.URI; +import org.chromium.url.GURL; +import org.chromium.url.Origin; +import org.chromium.url.URI; /** * See comment in: @@ -23,10 +26,17 @@ public class PaymentManifestDownloader { /** * Called on successful download of a payment method manifest. * + * @param paymentMethodManifestUrl The URL of the payment method manifest after all + * redirects and the optional HTTP Link rel=payment-method-manifest header have been + * followed. + * @param paymentMethodManifestOrigin The origin of the payment method manifest after all + * redirects and the optional HTTP Link rel=payment-method-manifest header have been + * followed. * @param content The successfully downloaded payment method manifest. */ @CalledByNative("ManifestDownloadCallback") - void onPaymentMethodManifestDownloadSuccess(String content); + void onPaymentMethodManifestDownloadSuccess( + URI paymentMethodManifestUrl, Origin paymentMethodManifestOrigin, String content); /** * Called on successful download of a web app manifest. @@ -68,27 +78,35 @@ public class PaymentManifestDownloader { /** * Downloads the payment method manifest file asynchronously. * - * @param methodName The payment method name that is a URI with HTTPS scheme. - * @param callback The callback to invoke when finished downloading. + * @param merchantOrigin The origin of the iframe that invoked the PaymentRequest API. + * @param methodName The payment method name that is a URI with HTTPS scheme. + * @param callback The callback to invoke when finished downloading. */ - public void downloadPaymentMethodManifest(URI methodName, ManifestDownloadCallback callback) { + public void downloadPaymentMethodManifest( + Origin merchantOrigin, URI methodName, ManifestDownloadCallback callback) { ThreadUtils.assertOnUiThread(); assert mNativeObject != 0; - PaymentManifestDownloaderJni.get().downloadPaymentMethodManifest( - mNativeObject, PaymentManifestDownloader.this, methodName, callback); + assert merchantOrigin != null; + PaymentManifestDownloaderJni.get().downloadPaymentMethodManifest(mNativeObject, + PaymentManifestDownloader.this, merchantOrigin, methodName, callback); } /** * Downloads the web app manifest file asynchronously. * - * @param webAppManifestUri The web app manifest URI with HTTPS scheme. - * @param callback The callback to invoke when finished downloading. + * @param paymentMethodManifestOrigin The origin of the payment method manifest that is pointing + * to this web app manifest. + * @param webAppManifestUri The web app manifest URI with HTTPS scheme. + * @param callback The callback to invoke when finished downloading. */ - public void downloadWebAppManifest(URI webAppManifestUri, ManifestDownloadCallback callback) { + public void downloadWebAppManifest(Origin paymentMethodManifestOrigin, URI webAppManifestUri, + ManifestDownloadCallback callback) { ThreadUtils.assertOnUiThread(); assert mNativeObject != 0; - PaymentManifestDownloaderJni.get().downloadWebAppManifest( - mNativeObject, PaymentManifestDownloader.this, webAppManifestUri, callback); + assert paymentMethodManifestOrigin != null; + PaymentManifestDownloaderJni.get().downloadWebAppManifest(mNativeObject, + PaymentManifestDownloader.this, paymentMethodManifestOrigin, webAppManifestUri, + callback); } /** Destroys the native downloader. */ @@ -99,20 +117,37 @@ public class PaymentManifestDownloader { mNativeObject = 0; } + /** @return An opaque origin to be used in tests. */ + @VisibleForTesting + public static Origin createOpaqueOriginForTest() { + return PaymentManifestDownloaderJni.get().createOpaqueOriginForTest(); + } + + /** + * Converts GURL to URI through string serialization. Needed because C++ knows only how to + * create Java GURL objects, but web payments uses URI, which is a subclass of GURL, so casting + * is not possible. + * + * TODO(crbug.com/1065577): Use GURL direclly everywhere in web payments. + * + * @param gurl The GURL to convert. Cannot be null. Must be valid. + * @return The equivalent URI. + */ @CalledByNative - private static String getUriString(URI methodName) { - return methodName.toString(); + public static URI convertGURLToURI(GURL gurl) { + return URI.create(gurl.getPossiblyInvalidSpec()); } @NativeMethods interface Natives { long init(WebContents webContents); void downloadPaymentMethodManifest(long nativePaymentManifestDownloaderAndroid, - PaymentManifestDownloader caller, URI methodName, + PaymentManifestDownloader caller, Origin merchantOrigin, URI methodName, ManifestDownloadCallback callback); void downloadWebAppManifest(long nativePaymentManifestDownloaderAndroid, - PaymentManifestDownloader caller, URI webAppManifestUri, - ManifestDownloadCallback callback); + PaymentManifestDownloader caller, Origin paymentMethodManifestOrigin, + URI webAppManifestUri, ManifestDownloadCallback callback); void destroy(long nativePaymentManifestDownloaderAndroid, PaymentManifestDownloader caller); + Origin createOpaqueOriginForTest(); } } diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentManifestParser.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentManifestParser.java index 7853fea3d8f..be5534681f2 100644 --- a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentManifestParser.java +++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentManifestParser.java @@ -9,8 +9,8 @@ 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.url.URI; -import java.net.URI; import java.net.URISyntaxException; /** Parses payment manifests in a utility process. */ @@ -76,14 +76,17 @@ public class PaymentManifestParser { /** * Parses the payment method manifest file asynchronously. * - * @param content The content to parse. + * @param manifestUrl The URL of the payment method manifest that is being parsed. Used for + * resolving the optionally relative URL of the default application. + * @param content The content to parse. * @param callback The callback to invoke when finished parsing. */ - public void parsePaymentMethodManifest(String content, ManifestParseCallback callback) { + public void parsePaymentMethodManifest( + URI manifestUrl, String content, ManifestParseCallback callback) { ThreadUtils.assertOnUiThread(); assert mNativePaymentManifestParserAndroid != 0; PaymentManifestParserJni.get().parsePaymentMethodManifest( - mNativePaymentManifestParserAndroid, content, callback); + mNativePaymentManifestParserAndroid, manifestUrl, content, callback); } /** @@ -135,8 +138,8 @@ public class PaymentManifestParser { interface Natives { long createPaymentManifestParserAndroid(WebContents webContents); void destroyPaymentManifestParserAndroid(long nativePaymentManifestParserAndroid); - void parsePaymentMethodManifest(long nativePaymentManifestParserAndroid, String content, - ManifestParseCallback callback); + void parsePaymentMethodManifest(long nativePaymentManifestParserAndroid, URI manifestUrl, + String content, ManifestParseCallback callback); void parseWebAppManifest(long nativePaymentManifestParserAndroid, String content, ManifestParseCallback callback); } 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 new file mode 100644 index 00000000000..8d8be4aee39 --- /dev/null +++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/IsReadyToPayServiceHelper.java @@ -0,0 +1,148 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.components.payments.intent; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; + +import org.chromium.IsReadyToPayService; +import org.chromium.IsReadyToPayServiceCallback; + +/** A helper to query the payment app's IsReadyToPay service. */ +public class IsReadyToPayServiceHelper + extends IsReadyToPayServiceCallback.Stub implements ServiceConnection { + /** The maximum number of milliseconds to wait for a response from a READY_TO_PAY service. */ + private static final long READY_TO_PAY_TIMEOUT_MS = 400; + /** The maximum number of milliseconds to wait for a connection to READY_TO_PAY service. */ + private static final long SERVICE_CONNECTION_TIMEOUT_MS = 1000; + + private final Context mContext; + + // This callback can be used only once, set to null after that. + private ResultHandler mResultHandler; + + private boolean mIsServiceBindingInitiated; + private boolean mIsReadyToPayQueried; + private Handler mHandler; + + /** The callback that returns the result (success or error) to the helper's caller. */ + public interface ResultHandler { + /** + * Invoked when the service receives the response. + * @param isReadyToPay The service response. + */ + void onIsReadyToPayServiceResponse(boolean isReadyToPay); + + /** Invoked when the service has any error. */ + void onIsReadyToPayServiceError(); + } + + /** + * The constructor starts the IsReadyToPay service. The result would be returned asynchronously + * with one callback. + * @param context The application context. Should not be null. + * @param isReadyToPayIntent The IsReaddyToPay intent created by {@link + * WebPaymentIntentHelper#createIsReadyToPayIntent}. Should not be null. + * @param resultHandler Invoked when the service's result is known. Should not be null. + */ + public IsReadyToPayServiceHelper( + Context context, Intent isReadyToPayIntent, ResultHandler resultHandler) { + assert context != null; + assert isReadyToPayIntent != null; + assert resultHandler != null; + mContext = context; + mResultHandler = resultHandler; + mHandler = new Handler(); + 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 + // find the service or if your client doesn't have permission to bind to it. If this + // value is true, you should later call unbindService(ServiceConnection) to release + // 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); + } catch (SecurityException e) { + // Intentionally blank, so mIsServiceBindingInitiated is false. + } + + if (!mIsServiceBindingInitiated) { + reportError(); + return; + } + + mHandler.postDelayed(() -> { + if (!mIsReadyToPayQueried) reportError(); + }, SERVICE_CONNECTION_TIMEOUT_MS); + } + + // ServiceConnection: + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + // Timeout could cause the null. + if (mResultHandler == null) return; + + IsReadyToPayService isReadyToPayService = IsReadyToPayService.Stub.asInterface(service); + if (isReadyToPayService == null) { + reportError(); + return; + } + + mIsReadyToPayQueried = true; + try { + isReadyToPayService.isReadyToPay(/*callback=*/this); + } catch (Throwable e) { + // Many undocumented exceptions are not caught in the remote Service but passed on + // to the Service caller, see writeException in Parcel.java. + reportError(); + return; + } + mHandler.postDelayed(this::reportError, READY_TO_PAY_TIMEOUT_MS); + } + + // "Called when a connection to the Service has been lost. This typically happens + // when the process hosting the service has crashed or been killed. This does not + // remove the ServiceConnection itself -- this binding to the service will remain + // active, and you will receive a call to onServiceConnected(ComponentName, IBinder) + // when the Service is next running." + // https://developer.android.com/reference/android/content/ServiceConnection.html#onServiceDisconnected(android.content.ComponentName) + @Override + public void onServiceDisconnected(ComponentName name) { + // Do not wait for the service to restart. + reportError(); + } + + // IsReadyToPayServiceCallback.Stub: + @Override + public void handleIsReadyToPay(boolean isReadyToPay) throws RemoteException { + if (mResultHandler == null) return; + mResultHandler.onIsReadyToPayServiceResponse(isReadyToPay); + mResultHandler = null; + destroy(); + } + + private void reportError() { + if (mResultHandler == null) return; + mResultHandler.onIsReadyToPayServiceError(); + mResultHandler = null; + destroy(); + } + + /** Clean up the resources that this helper has created. */ + private void destroy() { + if (mIsServiceBindingInitiated) { + // ServiceConnection "parameter must not be null." + // https://developer.android.com/reference/android/content/Context.html#unbindService(android.content.ServiceConnection) + mContext.unbindService(/*serviceConnection=*/this); + mIsServiceBindingInitiated = false; + } + mHandler.removeCallbacksAndMessages(null); + } +} 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 new file mode 100644 index 00000000000..5b268616e3d --- /dev/null +++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelper.java @@ -0,0 +1,547 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.components.payments.intent; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.JsonWriter; + +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; +import org.chromium.components.payments.intent.WebPaymentIntentHelperType.PaymentOptions; +import org.chromium.components.payments.intent.WebPaymentIntentHelperType.PaymentShippingOption; + +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; + +/** + * The helper that handles intent for AndroidPaymentApp. + */ +public class WebPaymentIntentHelper { + /** The action name for the Pay Intent. */ + public static final String ACTION_PAY = "org.chromium.intent.action.PAY"; + + // Freshest parameters sent to the payment app. + public static final String EXTRA_CERTIFICATE = "certificate"; + public static final String EXTRA_MERCHANT_NAME = "merchantName"; + public static final String EXTRA_METHOD_DATA = "methodData"; + public static final String EXTRA_METHOD_NAMES = "methodNames"; + public static final String EXTRA_MODIFIERS = "modifiers"; + public static final String EXTRA_PAYMENT_REQUEST_ID = "paymentRequestId"; + public static final String EXTRA_PAYMENT_REQUEST_ORIGIN = "paymentRequestOrigin"; + public static final String EXTRA_TOP_CERTIFICATE_CHAIN = "topLevelCertificateChain"; + 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_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"; + public static final String EXTRA_DEPRECATED_DATA = "data"; + public static final String EXTRA_DEPRECATED_DATA_MAP = "dataMap"; + public static final String EXTRA_DEPRECATED_DETAILS = "details"; + public static final String EXTRA_DEPRECATED_ID = "id"; + public static final String EXTRA_DEPRECATED_IFRAME_ORIGIN = "iframeOrigin"; + public static final String EXTRA_DEPRECATED_METHOD_NAME = "methodName"; + public static final String EXTRA_DEPRECATED_ORIGIN = "origin"; + + // Response from the payment app. + public static final String EXTRA_DEPRECATED_RESPONSE_INSTRUMENT_DETAILS = "instrumentDetails"; + public static final String EXTRA_RESPONSE_DETAILS = "details"; + public static final String EXTRA_RESPONSE_METHOD_NAME = "methodName"; + 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"; + + // Shipping address parsable and its fields, used in payment response and shippingAddressChange. + public static final String EXTRA_SHIPPING_ADDRESS = "shippingAddress"; + public static final String EXTRA_ADDRESS_COUNTRY = "country"; + public static final String EXTRA_ADDRESS_LINES = "addressLines"; + public static final String EXTRA_ADDRESS_REGION = "region"; + public static final String EXTRA_ADDRESS_CITY = "city"; + public static final String EXTRA_ADDRESS_DEPENDENT_LOCALITY = "dependentLocality"; + public static final String EXTRA_ADDRESS_POSTAL_CODE = "postalCode"; + public static final String EXTRA_ADDRESS_SORTING_CODE = "sortingCode"; + public static final String EXTRA_ADDRESS_ORGANIZATION = "organization"; + public static final String EXTRA_ADDRESS_RECIPIENT = "recipient"; + public static final String EXTRA_ADDRESS_PHONE = "phone"; + + private static final String EMPTY_JSON_DATA = "{}"; + + /** Invoked to report error for {@link #parsePaymentResponse}. */ + public interface PaymentErrorCallback { + /** @param errorString The string that explains the error. */ + void onPaymentError(String errorString); + } + + /** Invoked to receive parsed data for {@link #parsePaymentResponse}. */ + public interface PaymentSuccessCallback { + /** + * @param methodName The method name parsed from the intent response. + * @param details The instrument details parsed from the intent response. + * @param payerData The payer data parsed from the intent response. + */ + void onPaymentSuccess(String methodName, String details, PayerData payerData); + } + + /** + * Parse the Payment Intent response. + * @param resultCode Result code of the requested intent. + * @param data The intent response data. + * @param requestedPaymentOptions The merchant required payment options. This is used to + * populate relevant fields in payerData. + * @param errorCallback Callback to handle parsing errors. Invoked synchronously. + * @param successCallback Callback to receive the parsed data. Invoked synchronously. + **/ + public static void parsePaymentResponse(int resultCode, Intent data, + PaymentOptions requestedPaymentOptions, PaymentErrorCallback errorCallback, + PaymentSuccessCallback successCallback) { + if (data == null) { + errorCallback.onPaymentError(ErrorStrings.MISSING_INTENT_DATA); + } else if (data.getExtras() == null) { + errorCallback.onPaymentError(ErrorStrings.MISSING_INTENT_EXTRAS); + } else if (resultCode == Activity.RESULT_CANCELED) { + errorCallback.onPaymentError(ErrorStrings.RESULT_CANCELED); + } else if (resultCode != Activity.RESULT_OK) { + errorCallback.onPaymentError(String.format( + Locale.US, ErrorStrings.UNRECOGNIZED_ACTIVITY_RESULT, resultCode)); + } else { + // TODO(https://crbug.com/1026667): Validate presence of required fields and call + // errorCallback to make error handling consistent with Desktop. + String details = data.getExtras().getString(EXTRA_RESPONSE_DETAILS); + if (details == null) { + details = data.getExtras().getString(EXTRA_DEPRECATED_RESPONSE_INSTRUMENT_DETAILS); + } + if (details == null) details = EMPTY_JSON_DATA; + String methodName = data.getExtras().getString(EXTRA_RESPONSE_METHOD_NAME); + if (methodName == null) methodName = ""; + + PayerData payerData; + if (requestedPaymentOptions != null) { + // Create payer data based on requested payment options. + Address shippingAddress = !requestedPaymentOptions.requestShipping + ? new Address() + : new Address(getStringOrEmpty(data, EXTRA_ADDRESS_COUNTRY), + data.getExtras().getStringArray(EXTRA_ADDRESS_LINES), + getStringOrEmpty(data, EXTRA_ADDRESS_REGION), + getStringOrEmpty(data, EXTRA_ADDRESS_CITY), + getStringOrEmpty(data, EXTRA_ADDRESS_DEPENDENT_LOCALITY), + getStringOrEmpty(data, EXTRA_ADDRESS_POSTAL_CODE), + getStringOrEmpty(data, EXTRA_ADDRESS_SORTING_CODE), + getStringOrEmpty(data, EXTRA_ADDRESS_ORGANIZATION), + getStringOrEmpty(data, EXTRA_ADDRESS_RECIPIENT), + getStringOrEmpty(data, EXTRA_ADDRESS_PHONE)); + payerData = new PayerData(requestedPaymentOptions.requestPayerName + ? data.getExtras().getString(EXTRA_RESPONSE_PAYER_NAME) + : "", + requestedPaymentOptions.requestPayerPhone + ? data.getExtras().getString(EXTRA_RESPONSE_PAYER_PHONE) + : "", + requestedPaymentOptions.requestPayerEmail + ? data.getExtras().getString(EXTRA_RESPONSE_PAYER_EMAIL) + : "", + shippingAddress, + requestedPaymentOptions.requestShipping + ? data.getExtras().getString(EXTRA_SHIPPING_OPTION_ID) + : ""); + } else { + payerData = new PayerData(); + } + + successCallback.onPaymentSuccess( + /*methodName=*/methodName, /*details=*/details, payerData); + } + } + + /** + * Create an intent to invoke a native payment app. This method throws IllegalArgumentException + * for invalid arguments. + * + * @param packageName The name of the package of the payment app. Only non-empty string is + * allowed. + * @param activityName The name of the payment activity in the payment app. Only non-empty + * string is allowed. + * @param id The unique identifier of the PaymentRequest. Only non-empty string is allowed. + * @param merchantName The name of the merchant. Cannot be null.. + * @param schemelessOrigin The schemeless origin of this merchant. Only non-empty string is + * allowed. + * @param schemelessIframeOrigin The schemeless origin of the iframe that invoked + * PaymentRequest. Only non-empty string is allowed. + * @param certificateChain The site certificate chain of the merchant. Can be null for + * localhost or local file, which are secure contexts without SSL. Each byte array + * cannot be null. + * @param methodDataMap The payment-method specific data for all applicable payment methods, + * e.g., whether the app should be invoked in test or production, a merchant identifier, + * or a public key. The map and its values cannot be null. The map should have at + * least one entry. + * @param total The total amount. Cannot be null.. + * @param displayItems The shopping cart items. OK to be null. + * @param modifiers The relevant payment details modifiers. OK to be null. + * @param paymentOptions The relevant merchant requested payment options. OK to be null. + * @param shippingOptions Merchant specified available shipping options. Should be non-empty + * when paymentOptions.requestShipping is true. + * @return The intent to invoke the payment app. + */ + public static Intent createPayIntent(String packageName, String activityName, String id, + String merchantName, String schemelessOrigin, String schemelessIframeOrigin, + @Nullable byte[][] certificateChain, Map<String, PaymentMethodData> methodDataMap, + PaymentItem total, @Nullable List<PaymentItem> displayItems, + @Nullable Map<String, PaymentDetailsModifier> modifiers, + @Nullable PaymentOptions paymentOptions, + @Nullable List<PaymentShippingOption> shippingOptions) { + Intent payIntent = new Intent(); + checkStringNotEmpty(activityName, "activityName"); + checkStringNotEmpty(packageName, "packageName"); + payIntent.setClassName(packageName, activityName); + payIntent.setAction(ACTION_PAY); + payIntent.putExtras(buildPayIntentExtras(id, merchantName, schemelessOrigin, + schemelessIframeOrigin, certificateChain, methodDataMap, total, displayItems, + modifiers, paymentOptions, shippingOptions)); + return payIntent; + } + + /** + * Create an intent to invoke a service that can answer "is ready to pay" query, or null of + * none. + * + * @param packageName The name of the package of the payment app. Only non-empty string is + * allowed. + * @param serviceName The name of the service. Only non-empty string is allowed. + * @param schemelessOrigin The schemeless origin of this merchant. Only non-empty string is + * allowed. + * @param schemelessIframeOrigin The schemeless origin of the iframe that invoked + * PaymentRequest. Only non-empty string is allowed. + * @param certificateChain The site certificate chain of the merchant. Can be null for localhost + * or local file, which are secure contexts without SSL. Each byte array + * cannot be null. + * @param methodDataMap The payment-method specific data for all applicable payment methods, + * e.g., whether the app should be invoked in test or production, a merchant identifier, + * or a public key. The map should have at least one entry. + * @return The intent to invoke the service. + */ + public static Intent createIsReadyToPayIntent(String packageName, String serviceName, + String schemelessOrigin, String schemelessIframeOrigin, + @Nullable byte[][] certificateChain, Map<String, PaymentMethodData> methodDataMap) { + Intent isReadyToPayIntent = new Intent(); + checkStringNotEmpty(serviceName, "serviceName"); + checkStringNotEmpty(packageName, "packageName"); + isReadyToPayIntent.setClassName(packageName, serviceName); + + checkStringNotEmpty(schemelessOrigin, "schemelessOrigin"); + checkStringNotEmpty(schemelessIframeOrigin, "schemelessIframeOrigin"); + // certificateChain is ok to be null, left unchecked here. + checkNotEmpty(methodDataMap, "methodDataMap"); + isReadyToPayIntent.putExtras(buildExtras(/*id=*/null, + /*merchantName=*/null, schemelessOrigin, schemelessIframeOrigin, certificateChain, + methodDataMap, /*total=*/null, /*displayItems=*/null, /*modifiers=*/null, + /*paymentOptions=*/null, + /*shippingOptions=*/null)); + return isReadyToPayIntent; + } + + private static void checkNotEmpty(Map map, String name) { + if (map == null || map.isEmpty()) { + throw new IllegalArgumentException(name + " should not be null or empty."); + } + } + + private static void checkStringNotEmpty(String value, String name) { + if (TextUtils.isEmpty(value)) { + throw new IllegalArgumentException(name + " should not be null or empty."); + } + } + private static void checkNotNull(Object value, String name) { + if (value == null) throw new IllegalArgumentException(name + " should not be null."); + } + + private static Bundle buildPayIntentExtras(String id, String merchantName, + String schemelessOrigin, String schemelessIframeOrigin, + @Nullable byte[][] certificateChain, Map<String, PaymentMethodData> methodDataMap, + PaymentItem total, @Nullable List<PaymentItem> displayItems, + @Nullable Map<String, PaymentDetailsModifier> modifiers, + @Nullable PaymentOptions paymentOptions, + @Nullable List<PaymentShippingOption> shippingOptions) { + // The following checks follow the order of the parameters. + checkStringNotEmpty(id, "id"); + checkNotNull(merchantName, "merchantName"); + + checkStringNotEmpty(schemelessOrigin, "schemelessOrigin"); + checkStringNotEmpty(schemelessIframeOrigin, "schemelessIframeOrigin"); + + // certificateChain is ok to be null, left unchecked here. + + checkNotEmpty(methodDataMap, "methodDataMap"); + checkNotNull(total, "total"); + + // displayItems is ok to be null, left unchecked here. + // modifiers is ok to be null, left unchecked here. + + // shippingOptions should not be null when shipping is requested. + if (paymentOptions != null && paymentOptions.requestShipping + && (shippingOptions == null || shippingOptions.isEmpty())) { + throw new IllegalArgumentException( + "shippingOptions should not be null or empty when shipping is requested."); + } + + return buildExtras(id, merchantName, schemelessOrigin, schemelessIframeOrigin, + certificateChain, methodDataMap, total, displayItems, modifiers, paymentOptions, + shippingOptions); + } + + // id, merchantName, total are ok to be null only for {@link #createIsReadyToPayIntent}. + private static Bundle buildExtras(@Nullable String id, @Nullable String merchantName, + String schemelessOrigin, String schemelessIframeOrigin, + @Nullable byte[][] certificateChain, Map<String, PaymentMethodData> methodDataMap, + @Nullable PaymentItem total, @Nullable List<PaymentItem> displayItems, + @Nullable Map<String, PaymentDetailsModifier> modifiers, + @Nullable PaymentOptions paymentOptions, + @Nullable List<PaymentShippingOption> shippingOptions) { + Bundle extras = new Bundle(); + + if (id != null) extras.putString(EXTRA_PAYMENT_REQUEST_ID, id); + + if (merchantName != null) extras.putString(EXTRA_MERCHANT_NAME, merchantName); + + assert !TextUtils.isEmpty(schemelessOrigin); + extras.putString(EXTRA_TOP_ORIGIN, schemelessOrigin); + + assert !TextUtils.isEmpty(schemelessIframeOrigin); + extras.putString(EXTRA_PAYMENT_REQUEST_ORIGIN, schemelessIframeOrigin); + + Parcelable[] serializedCertificateChain = null; + if (certificateChain != null && certificateChain.length > 0) { + serializedCertificateChain = buildCertificateChain(certificateChain); + extras.putParcelableArray(EXTRA_TOP_CERTIFICATE_CHAIN, serializedCertificateChain); + } + + assert methodDataMap != null && !methodDataMap.isEmpty(); + extras.putStringArrayList(EXTRA_METHOD_NAMES, new ArrayList<>(methodDataMap.keySet())); + + Bundle methodDataBundle = new Bundle(); + for (Map.Entry<String, PaymentMethodData> methodData : methodDataMap.entrySet()) { + checkNotNull(methodData.getValue(), "methodDataMap's entry value"); + methodDataBundle.putString(methodData.getKey(), methodData.getValue().stringifiedData); + } + extras.putParcelable(EXTRA_METHOD_DATA, methodDataBundle); + + if (modifiers != null) { + extras.putString(EXTRA_MODIFIERS, serializeModifiers(modifiers.values())); + } + + if (total != null) { + String serializedTotalAmount = serializeTotalAmount(total.amount); + extras.putString(EXTRA_TOTAL, + serializedTotalAmount == null ? EMPTY_JSON_DATA : serializedTotalAmount); + } + + if (paymentOptions != null) { + extras.putStringArrayList(EXTRA_PAYMENT_OPTIONS, paymentOptions.asStringArrayList()); + } + + // ShippingOptions are populated only when shipping is requested. + if (paymentOptions != null && paymentOptions.requestShipping) { + Parcelable[] serializedShippingOptionList = buildShippingOptionList(shippingOptions); + extras.putParcelableArray(EXTRA_SHIPPING_OPTIONS, serializedShippingOptionList); + } + + return addDeprecatedExtras(id, schemelessOrigin, schemelessIframeOrigin, + serializedCertificateChain, methodDataMap, methodDataBundle, total, displayItems, + extras); + } + + private static Bundle addDeprecatedExtras(@Nullable String id, String schemelessOrigin, + String schemelessIframeOrigin, @Nullable Parcelable[] serializedCertificateChain, + Map<String, PaymentMethodData> methodDataMap, Bundle methodDataBundle, + @Nullable PaymentItem total, @Nullable List<PaymentItem> displayItems, Bundle extras) { + if (id != null) extras.putString(EXTRA_DEPRECATED_ID, id); + + extras.putString(EXTRA_DEPRECATED_ORIGIN, schemelessOrigin); + + extras.putString(EXTRA_DEPRECATED_IFRAME_ORIGIN, schemelessIframeOrigin); + + if (serializedCertificateChain != null) { + extras.putParcelableArray( + EXTRA_DEPRECATED_CERTIFICATE_CHAIN, serializedCertificateChain); + } + + String methodName = methodDataMap.entrySet().iterator().next().getKey(); + extras.putString(EXTRA_DEPRECATED_METHOD_NAME, methodName); + + PaymentMethodData firstMethodData = methodDataMap.get(methodName); + extras.putString(EXTRA_DEPRECATED_DATA, + firstMethodData == null ? EMPTY_JSON_DATA : firstMethodData.stringifiedData); + + extras.putParcelable(EXTRA_DEPRECATED_DATA_MAP, methodDataBundle); + + String details = deprecatedSerializeDetails(total, displayItems); + extras.putString(EXTRA_DEPRECATED_DETAILS, details == null ? EMPTY_JSON_DATA : details); + + return extras; + } + + private static Parcelable[] buildCertificateChain(byte[][] certificateChain) { + Parcelable[] result = new Parcelable[certificateChain.length]; + for (int i = 0; i < certificateChain.length; i++) { + Bundle bundle = new Bundle(); + checkNotNull(certificateChain[i], "certificateChain[" + i + "]"); + bundle.putByteArray(EXTRA_CERTIFICATE, certificateChain[i]); + result[i] = bundle; + } + 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; + } + return result; + } + + private static String deprecatedSerializeDetails( + @Nullable PaymentItem total, @Nullable List<PaymentItem> displayItems) { + StringWriter stringWriter = new StringWriter(); + JsonWriter json = new JsonWriter(stringWriter); + try { + // details {{{ + json.beginObject(); + + if (total != null) { + // total {{{ + json.name("total"); + serializeTotal(total, json); + // }}} total + } + + // displayitems {{{ + if (displayItems != null) { + json.name("displayItems").beginArray(); + // Do not pass any display items to the payment app. + json.endArray(); + } + // }}} displayItems + + json.endObject(); + // }}} details + } catch (IOException e) { + return null; + } + + 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 new file mode 100644 index 00000000000..14e17efde93 --- /dev/null +++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelperType.java @@ -0,0 +1,115 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.components.payments.intent; + +import androidx.annotation.Nullable; + +import java.util.ArrayList; + +/** + * 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 + * 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 + * ready to pay” parameters</a> + */ +public final class WebPaymentIntentHelperType { + /** + * The class that corresponds to mojom.PaymentCurrencyAmount, with minimally required fields. + */ + public static final class PaymentCurrencyAmount { + public final String currency; + public final String value; + public PaymentCurrencyAmount(String currency, String value) { + this.currency = currency; + this.value = value; + } + } + + /** The class that corresponds mojom.PaymentItem, with minimally required fields. */ + public static final class PaymentItem { + public final PaymentCurrencyAmount amount; + public PaymentItem(PaymentCurrencyAmount amount) { + this.amount = amount; + } + } + + /** The class that corresponds mojom.PaymentDetailsModifier, with minimally required fields. */ + public static final class PaymentDetailsModifier { + public final PaymentItem total; + public final PaymentMethodData methodData; + public PaymentDetailsModifier(PaymentItem total, PaymentMethodData methodData) { + this.total = total; + this.methodData = methodData; + } + } + + /** The class that corresponds mojom.PaymentMethodData, with minimally required fields. */ + public static final class PaymentMethodData { + public final String supportedMethod; + public final String stringifiedData; + public PaymentMethodData(String supportedMethod, String stringifiedData) { + this.supportedMethod = supportedMethod; + this.stringifiedData = stringifiedData; + } + } + + /** The class that mirrors mojom.PaymentShippingOption. */ + public static final class PaymentShippingOption { + public final String id; + public final String label; + public final String amountCurrency; + public final String amountValue; + public final boolean selected; + public PaymentShippingOption(String id, String label, String amountCurrency, + String amountValue, boolean selected) { + this.id = id; + this.label = label; + this.amountCurrency = amountCurrency; + this.amountValue = amountValue; + this.selected = selected; + } + } + + /** The class that mirrors mojom.PaymentOptions. */ + public static final class PaymentOptions { + public final boolean requestPayerName; + public final boolean requestPayerEmail; + public final boolean requestPayerPhone; + public final boolean requestShipping; + public final String shippingType; + + public PaymentOptions(boolean requestPayerName, boolean requestPayerEmail, + boolean requestPayerPhone, boolean requestShipping, @Nullable String shippingType) { + this.requestPayerName = requestPayerName; + this.requestPayerEmail = requestPayerEmail; + this.requestPayerPhone = requestPayerPhone; + this.requestShipping = requestShipping; + this.shippingType = shippingType; + } + + /** + * @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. + */ + 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); + } + return paymentOptionList; + } + } +} 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 c58d0ea5eea..2ca0ff39f71 100644 --- a/chromium/components/payments/content/android/java_templates/ErrorStrings.java.tmpl +++ b/chromium/components/payments/content/android/java_templates/ErrorStrings.java.tmpl @@ -50,7 +50,7 @@ public abstract class ErrorStrings {{ public static final String UNRECOGNIZED_ACTIVITY_RESULT = "Payment app returned unrecognized activity result %d."; - public static final String MICROTRANSACTION_UI_SUPPRESSED = "Microtransaction UI suppressed."; + public static final String MINIMAL_UI_SUPPRESSED = "Payment minimal UI suppressed."; // Prevent instantiation. private ErrorStrings() {{}} diff --git a/chromium/components/payments/content/android/payment_manifest_downloader_android.cc b/chromium/components/payments/content/android/payment_manifest_downloader_android.cc index bc4a9ef0d42..3a2eaa1f3f1 100644 --- a/chromium/components/payments/content/android/payment_manifest_downloader_android.cc +++ b/chromium/components/payments/content/android/payment_manifest_downloader_android.cc @@ -15,7 +15,9 @@ #include "content/public/browser/storage_partition.h" #include "content/public/browser/web_contents.h" #include "services/network/public/cpp/shared_url_loader_factory.h" +#include "url/android/gurl_android.h" #include "url/gurl.h" +#include "url/origin.h" namespace payments { namespace { @@ -40,6 +42,9 @@ class DownloadCallback { } else { Java_ManifestDownloadCallback_onPaymentMethodManifestDownloadSuccess( env, jcallback_, + Java_PaymentManifestDownloader_convertGURLToURI( + env, url::GURLAndroid::FromNativeGURL(env, url_after_redirects)), + url::Origin::Create(url_after_redirects).CreateJavaObject(), base::android::ConvertUTF8ToJavaString(env, content)); } } @@ -78,11 +83,12 @@ PaymentManifestDownloaderAndroid::~PaymentManifestDownloaderAndroid() {} void PaymentManifestDownloaderAndroid::DownloadPaymentMethodManifest( JNIEnv* env, const base::android::JavaParamRef<jobject>& jcaller, + const base::android::JavaParamRef<jobject>& jmerchant_origin, const base::android::JavaParamRef<jobject>& juri, const base::android::JavaParamRef<jobject>& jcallback) { downloader_.DownloadPaymentMethodManifest( - GURL(base::android::ConvertJavaStringToUTF8( - env, Java_PaymentManifestDownloader_getUriString(env, juri))), + url::Origin::FromJavaObject(jmerchant_origin), + *url::GURLAndroid::ToNativeGURL(env, juri), base::BindOnce(&DownloadCallback::OnPaymentMethodManifestDownload, std::make_unique<DownloadCallback>(jcallback))); } @@ -90,11 +96,12 @@ void PaymentManifestDownloaderAndroid::DownloadPaymentMethodManifest( void PaymentManifestDownloaderAndroid::DownloadWebAppManifest( JNIEnv* env, const base::android::JavaParamRef<jobject>& jcaller, + const base::android::JavaParamRef<jobject>& jpayment_method_manifest_origin, const base::android::JavaParamRef<jobject>& juri, const base::android::JavaParamRef<jobject>& jcallback) { downloader_.DownloadWebAppManifest( - GURL(base::android::ConvertJavaStringToUTF8( - env, Java_PaymentManifestDownloader_getUriString(env, juri))), + url::Origin::FromJavaObject(jpayment_method_manifest_origin), + *url::GURLAndroid::ToNativeGURL(env, juri), base::BindOnce(&DownloadCallback::OnWebAppManifestDownload, std::make_unique<DownloadCallback>(jcallback))); } @@ -122,4 +129,10 @@ static jlong JNI_PaymentManifestDownloader_Init( ->GetURLLoaderFactoryForBrowserProcess())); } +// Static free function declared and called directly from java. +static base::android::ScopedJavaLocalRef<jobject> +JNI_PaymentManifestDownloader_CreateOpaqueOriginForTest(JNIEnv* unused_env) { + return url::Origin().CreateJavaObject(); +} + } // namespace payments diff --git a/chromium/components/payments/content/android/payment_manifest_downloader_android.h b/chromium/components/payments/content/android/payment_manifest_downloader_android.h index 05a4d76e109..1d75662679f 100644 --- a/chromium/components/payments/content/android/payment_manifest_downloader_android.h +++ b/chromium/components/payments/content/android/payment_manifest_downloader_android.h @@ -32,12 +32,15 @@ class PaymentManifestDownloaderAndroid { void DownloadPaymentMethodManifest( JNIEnv* env, const base::android::JavaParamRef<jobject>& jcaller, + const base::android::JavaParamRef<jobject>& jmerchant_origin, const base::android::JavaParamRef<jobject>& juri, const base::android::JavaParamRef<jobject>& jcallback); void DownloadWebAppManifest( JNIEnv* env, const base::android::JavaParamRef<jobject>& jcaller, + const base::android::JavaParamRef<jobject>& + jpayment_method_manifest_origin, const base::android::JavaParamRef<jobject>& juri, const base::android::JavaParamRef<jobject>& jcallback); diff --git a/chromium/components/payments/content/android/payment_manifest_parser_android.cc b/chromium/components/payments/content/android/payment_manifest_parser_android.cc index 98abacdb4a3..4ce8749ab7f 100644 --- a/chromium/components/payments/content/android/payment_manifest_parser_android.cc +++ b/chromium/components/payments/content/android/payment_manifest_parser_android.cc @@ -19,6 +19,7 @@ #include "components/payments/content/developer_console_logger.h" #include "components/payments/core/error_logger.h" #include "content/public/browser/web_contents.h" +#include "url/android/gurl_android.h" #include "url/gurl.h" namespace payments { @@ -131,9 +132,11 @@ PaymentManifestParserAndroid::~PaymentManifestParserAndroid() {} void PaymentManifestParserAndroid::ParsePaymentMethodManifest( JNIEnv* env, + const base::android::JavaParamRef<jobject>& jmanifest_url, const base::android::JavaParamRef<jstring>& jcontent, const base::android::JavaParamRef<jobject>& jcallback) { parser_.ParsePaymentMethodManifest( + *url::GURLAndroid::ToNativeGURL(env, jmanifest_url), base::android::ConvertJavaStringToUTF8(env, jcontent), base::BindOnce(&ParseCallback::OnPaymentMethodManifestParsed, std::make_unique<ParseCallback>(jcallback))); diff --git a/chromium/components/payments/content/android/payment_manifest_parser_android.h b/chromium/components/payments/content/android/payment_manifest_parser_android.h index c201077a9cf..56cf43e1e85 100644 --- a/chromium/components/payments/content/android/payment_manifest_parser_android.h +++ b/chromium/components/payments/content/android/payment_manifest_parser_android.h @@ -25,6 +25,7 @@ class PaymentManifestParserAndroid { void ParsePaymentMethodManifest( JNIEnv* env, + const base::android::JavaParamRef<jobject>& jmanifest_url, const base::android::JavaParamRef<jstring>& jcontent, const base::android::JavaParamRef<jobject>& jcallback); diff --git a/chromium/components/payments/content/autofill_payment_app_factory.cc b/chromium/components/payments/content/autofill_payment_app_factory.cc index 04df6293989..5e39b1598f0 100644 --- a/chromium/components/payments/content/autofill_payment_app_factory.cc +++ b/chromium/components/payments/content/autofill_payment_app_factory.cc @@ -25,26 +25,12 @@ AutofillPaymentAppFactory::ConvertCardToPaymentAppIfSupportedNetwork( autofill::data_util::GetPaymentRequestData(card.network()) .basic_card_issuer_network; if (!delegate->GetSpec()->supported_card_networks_set().count( - basic_card_network) || - !delegate->GetSpec()->supported_card_types_set().count( - card.card_type())) { + basic_card_network)) { return nullptr; } - // The total number of card types: credit, debit, prepaid, unknown. - constexpr size_t kTotalNumberOfCardTypes = 4U; - - // Whether the card type (credit, debit, prepaid) matches the type that the - // merchant has requested exactly. This should be false for unknown card - // types, if the merchant cannot accept some card types. - bool matches_merchant_card_type_exactly = - card.card_type() != autofill::CreditCard::CARD_TYPE_UNKNOWN || - delegate->GetSpec()->supported_card_types_set().size() == - kTotalNumberOfCardTypes; - auto app = std::make_unique<AutofillPaymentApp>( - basic_card_network, card, matches_merchant_card_type_exactly, - delegate->GetBillingProfiles(), + basic_card_network, card, delegate->GetBillingProfiles(), delegate->GetPaymentRequestDelegate()->GetApplicationLocale(), delegate->GetPaymentRequestDelegate()); @@ -60,6 +46,13 @@ AutofillPaymentAppFactory::AutofillPaymentAppFactory() AutofillPaymentAppFactory::~AutofillPaymentAppFactory() = default; void AutofillPaymentAppFactory::Create(base::WeakPtr<Delegate> delegate) { + // No need to create autofill payment apps if native app creation is skipped + // because autofill payment apps are created completely by the Java factory. + if (delegate->SkipCreatingNativePaymentApps()) { + delegate->OnDoneCreatingPaymentApps(); + return; + } + const std::vector<autofill::CreditCard*>& cards = delegate->GetPaymentRequestDelegate() ->GetPersonalDataManager() diff --git a/chromium/components/payments/content/icon/BUILD.gn b/chromium/components/payments/content/icon/BUILD.gn index 6054837f849..b031c0b8bd1 100644 --- a/chromium/components/payments/content/icon/BUILD.gn +++ b/chromium/components/payments/content/icon/BUILD.gn @@ -10,7 +10,5 @@ jumbo_static_library("icon") { "icon_size.h", ] - deps = [ - "//ui/display", - ] + deps = [ "//ui/display" ] } diff --git a/chromium/components/payments/content/installable_payment_app_crawler.cc b/chromium/components/payments/content/installable_payment_app_crawler.cc index 485a289010f..8fe2a7570eb 100644 --- a/chromium/components/payments/content/installable_payment_app_crawler.cc +++ b/chromium/components/payments/content/installable_payment_app_crawler.cc @@ -15,10 +15,13 @@ #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" +#include "content/public/browser/global_routing_id.h" #include "content/public/browser/manifest_icon_downloader.h" #include "content/public/browser/payment_app_provider.h" #include "content/public/browser/permission_controller.h" #include "content/public/browser/permission_type.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/render_process_host.h" #include "content/public/browser/web_contents.h" #include "net/base/registry_controlled_domains/registry_controlled_domain.h" #include "third_party/blink/public/common/manifest/manifest.h" @@ -26,19 +29,27 @@ #include "third_party/blink/public/mojom/devtools/console_message.mojom.h" #include "ui/gfx/geometry/size.h" #include "url/gurl.h" -#include "url/origin.h" namespace payments { // TODO(crbug.com/782270): Use cache to accelerate crawling procedure. -// TODO(crbug.com/782270): Add integration tests for this class. InstallablePaymentAppCrawler::InstallablePaymentAppCrawler( + const url::Origin& merchant_origin, + content::RenderFrameHost* initiator_render_frame_host, content::WebContents* web_contents, PaymentManifestDownloader* downloader, PaymentManifestParser* parser, PaymentManifestWebDataService* cache) : WebContentsObserver(web_contents), log_(web_contents), + merchant_origin_(merchant_origin), + initiator_frame_routing_id_( + initiator_render_frame_host && + initiator_render_frame_host->GetProcess() + ? content::GlobalFrameRoutingId( + initiator_render_frame_host->GetProcess()->GetID(), + initiator_render_frame_host->GetRoutingID()) + : content::GlobalFrameRoutingId()), downloader_(downloader), parser_(parser), number_of_payment_method_manifest_to_download_(0), @@ -79,7 +90,7 @@ void InstallablePaymentAppCrawler::Start( number_of_payment_method_manifest_to_download_ = manifests_to_download.size(); for (const auto& url : manifests_to_download) { downloader_->DownloadPaymentMethodManifest( - url, + merchant_origin_, url, base::BindOnce( &InstallablePaymentAppCrawler::OnPaymentMethodManifestDownloaded, weak_ptr_factory_.GetWeakPtr(), url)); @@ -120,7 +131,7 @@ void InstallablePaymentAppCrawler::OnPaymentMethodManifestDownloaded( number_of_payment_method_manifest_to_parse_++; parser_->ParsePaymentMethodManifest( - content, base::BindOnce( + method_manifest_url, content, base::BindOnce( &InstallablePaymentAppCrawler::OnPaymentMethodManifestParsed, weak_ptr_factory_.GetWeakPtr(), method_manifest_url, method_manifest_url_after_redirects)); @@ -172,6 +183,7 @@ void InstallablePaymentAppCrawler::OnPaymentMethodManifestParsed( number_of_web_app_manifest_to_download_++; downloaded_web_app_manifests_.insert(web_app_manifest_url); downloader_->DownloadWebAppManifest( + url::Origin::Create(method_manifest_url_after_redirects), web_app_manifest_url, base::BindOnce( &InstallablePaymentAppCrawler::OnPaymentWebAppManifestDownloaded, @@ -222,11 +234,17 @@ void InstallablePaymentAppCrawler::OnPaymentWebAppInstallationInfo( std::unique_ptr<std::vector<PaymentManifestParser::WebAppIcon>> icons) { number_of_web_app_manifest_to_parse_--; + // Only download and decode payment app's icon if it is valid and stored. if (CompleteAndStorePaymentWebAppInfoIfValid( method_manifest_url, web_app_manifest_url, std::move(app_info))) { - // Only download and decode payment app's icon if it is valid and stored. - DownloadAndDecodeWebAppIcon(method_manifest_url, web_app_manifest_url, - std::move(icons)); + if (!DownloadAndDecodeWebAppIcon(method_manifest_url, web_app_manifest_url, + std::move(icons))) { + std::string error_message = base::ReplaceStringPlaceholders( + errors::kInvalidWebAppIcon, {web_app_manifest_url.spec()}, nullptr); + SetFirstError(error_message); + // App without a valid icon is not JIT installable. + installable_apps_.erase(method_manifest_url); + } } FinishCrawlingPaymentAppsIfReady(); @@ -318,7 +336,7 @@ bool InstallablePaymentAppCrawler::CompleteAndStorePaymentWebAppInfoIfValid( return true; } -void InstallablePaymentAppCrawler::DownloadAndDecodeWebAppIcon( +bool InstallablePaymentAppCrawler::DownloadAndDecodeWebAppIcon( const GURL& method_manifest_url, const GURL& web_app_manifest_url, std::unique_ptr<std::vector<PaymentManifestParser::WebAppIcon>> icons) { @@ -328,7 +346,7 @@ void InstallablePaymentAppCrawler::DownloadAndDecodeWebAppIcon( "web app manifest \"" + web_app_manifest_url.spec() + "\" for payment handler manifest \"" + method_manifest_url.spec() + "\"."); - return; + return false; } std::vector<blink::Manifest::ImageResource> manifest_icons; @@ -371,7 +389,7 @@ void InstallablePaymentAppCrawler::DownloadAndDecodeWebAppIcon( web_app_manifest_url.spec() + "\" for payment handler manifest \"" + method_manifest_url.spec() + "\"."); - return; + return false; } // Stop if the web_contents is gone. @@ -381,7 +399,7 @@ void InstallablePaymentAppCrawler::DownloadAndDecodeWebAppIcon( "manifest \"" + web_app_manifest_url.spec() + "\" for payment handler manifest \"" + method_manifest_url.spec() + "\")."); - return; + return false; } gfx::NativeView native_view = web_contents()->GetNativeView(); @@ -395,10 +413,29 @@ void InstallablePaymentAppCrawler::DownloadAndDecodeWebAppIcon( web_app_manifest_url.spec() + "\" for payment handler manifest \"" + method_manifest_url.spec() + "\"."); - return; + return false; } number_of_web_app_icons_to_download_and_decode_++; + + // If the initiator frame doesn't exists any more, e.g. the frame has + // navigated away, don't download the icon. + // TODO(crbug.com/1058840): Move this sanity check to ManifestIconDownloader + // after DownloadImage refactor is done. + content::RenderFrameHost* render_frame_host = + content::RenderFrameHost::FromID(initiator_frame_routing_id_); + if (!render_frame_host || !render_frame_host->IsCurrent() || + content::WebContents::FromRenderFrameHost(render_frame_host) != + web_contents()) { + // Post the result back asynchronously. + base::PostTask( + FROM_HERE, {content::BrowserThread::UI}, + base::BindOnce( + &InstallablePaymentAppCrawler::FinishCrawlingPaymentAppsIfReady, + weak_ptr_factory_.GetWeakPtr())); + return false; + } + bool can_download_icon = content::ManifestIconDownloader::Download( web_contents(), downloader_->FindTestServerURL(best_icon_url), IconSizeCalculator::IdealIconHeight(native_view), @@ -407,8 +444,10 @@ void InstallablePaymentAppCrawler::DownloadAndDecodeWebAppIcon( &InstallablePaymentAppCrawler::OnPaymentWebAppIconDownloadAndDecoded, weak_ptr_factory_.GetWeakPtr(), method_manifest_url, web_app_manifest_url), - false /* square_only */); + false, /* square_only */ + initiator_frame_routing_id_); DCHECK(can_download_icon); + return can_download_icon; } void InstallablePaymentAppCrawler::OnPaymentWebAppIconDownloadAndDecoded( @@ -416,16 +455,19 @@ 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); } else { - auto it = installable_apps_.find(method_manifest_url); - DCHECK(it != installable_apps_.end()); - DCHECK(IsSameOriginWith(GURL(it->second->sw_scope), web_app_manifest_url)); - it->second->icon = std::make_unique<SkBitmap>(icon); } diff --git a/chromium/components/payments/content/installable_payment_app_crawler.h b/chromium/components/payments/content/installable_payment_app_crawler.h index 6e9d7447f33..00fc072767a 100644 --- a/chromium/components/payments/content/installable_payment_app_crawler.h +++ b/chromium/components/payments/content/installable_payment_app_crawler.h @@ -19,12 +19,15 @@ #include "components/payments/content/utility/payment_manifest_parser.h" #include "components/payments/content/web_app_manifest.h" #include "components/payments/core/payment_manifest_downloader.h" +#include "content/public/browser/global_routing_id.h" #include "content/public/browser/web_contents_observer.h" #include "third_party/blink/public/mojom/payments/payment_request.mojom.h" +#include "url/origin.h" class GURL; namespace content { +class RenderFrameHost; class WebContents; } @@ -39,13 +42,21 @@ class InstallablePaymentAppCrawler : public content::WebContentsObserver { std::map<GURL, std::unique_ptr<WebAppInstallationInfo>>, const std::string& error_message)>; + // |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'. + // |initiator_render_frame_host| is the iframe for |merchant_origin|. + // // The owner of InstallablePaymentAppCrawler owns |downloader|, |parser| and // |cache|. They should live until |finished_using_resources| parameter to // Start() method is called. - InstallablePaymentAppCrawler(content::WebContents* web_contents, - PaymentManifestDownloader* downloader, - PaymentManifestParser* parser, - PaymentManifestWebDataService* cache); + InstallablePaymentAppCrawler( + const url::Origin& merchant_origin, + content::RenderFrameHost* initiator_render_frame_host, + content::WebContents* web_contents, + PaymentManifestDownloader* downloader, + PaymentManifestParser* parser, + PaymentManifestWebDataService* cache); ~InstallablePaymentAppCrawler() override; // Starts the crawling process. All the url based payment methods in @@ -89,7 +100,9 @@ class InstallablePaymentAppCrawler : public content::WebContentsObserver { const GURL& method_manifest_url, const GURL& web_app_manifest_url, std::unique_ptr<WebAppInstallationInfo> app_info); - void DownloadAndDecodeWebAppIcon( + // Returns true if an icon can get downloaded for the web app with the given + // manifest. + bool DownloadAndDecodeWebAppIcon( const GURL& method_manifest_url, const GURL& web_app_manifest_url, std::unique_ptr<std::vector<PaymentManifestParser::WebAppIcon>> icons); @@ -100,6 +113,8 @@ class InstallablePaymentAppCrawler : public content::WebContentsObserver { void SetFirstError(const std::string& error_message); DeveloperConsoleLogger log_; + const url::Origin merchant_origin_; + const content::GlobalFrameRoutingId initiator_frame_routing_id_; PaymentManifestDownloader* downloader_; PaymentManifestParser* parser_; FinishedCrawlingCallback callback_; diff --git a/chromium/components/payments/content/manifest_verifier.cc b/chromium/components/payments/content/manifest_verifier.cc index 3549be8b719..0a7fb9b4ae4 100644 --- a/chromium/components/payments/content/manifest_verifier.cc +++ b/chromium/components/payments/content/manifest_verifier.cc @@ -58,11 +58,13 @@ void EnableMethodManifestUrlForSupportedApps( } // namespace -ManifestVerifier::ManifestVerifier(content::WebContents* web_contents, +ManifestVerifier::ManifestVerifier(const url::Origin& merchant_origin, + content::WebContents* web_contents, PaymentManifestDownloader* downloader, PaymentManifestParser* parser, PaymentManifestWebDataService* cache) - : log_(web_contents), + : merchant_origin_(merchant_origin), + log_(web_contents), downloader_(downloader), parser_(parser), cache_(cache), @@ -203,7 +205,7 @@ void ManifestVerifier::OnWebDataServiceRequestDone( } downloader_->DownloadPaymentMethodManifest( - method_manifest_url, + merchant_origin_, method_manifest_url, base::BindOnce(&ManifestVerifier::OnPaymentMethodManifestDownloaded, weak_ptr_factory_.GetWeakPtr(), method_manifest_url)); } @@ -233,7 +235,7 @@ void ManifestVerifier::OnPaymentMethodManifestDownloaded( } parser_->ParsePaymentMethodManifest( - content, + method_manifest_url, content, base::BindOnce(&ManifestVerifier::OnPaymentMethodManifestParsed, weak_ptr_factory_.GetWeakPtr(), method_manifest_url)); } diff --git a/chromium/components/payments/content/manifest_verifier.h b/chromium/components/payments/content/manifest_verifier.h index 80e608a77e4..3d05c7cf056 100644 --- a/chromium/components/payments/content/manifest_verifier.h +++ b/chromium/components/payments/content/manifest_verifier.h @@ -20,6 +20,7 @@ #include "components/webdata/common/web_data_service_consumer.h" #include "content/public/browser/payment_app_provider.h" #include "content/public/browser/web_contents_observer.h" +#include "url/origin.h" class GURL; @@ -62,10 +63,16 @@ class ManifestVerifier final : public WebDataServiceConsumer { const std::string& error_message)>; // Creates the verifier and starts up the parser utility process. + // + // |merchant_origin| should be 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'. + // // The owner of ManifestVerifier owns |downloader|, |parser| and // |cache|. They should live until |finished_using_resources| parameter to // Verify() method is called. - ManifestVerifier(content::WebContents* web_contents, + ManifestVerifier(const url::Origin& merchant_origin, + content::WebContents* web_contents, PaymentManifestDownloader* downloader, PaymentManifestParser* parser, PaymentManifestWebDataService* cache); @@ -102,6 +109,7 @@ class ManifestVerifier final : public WebDataServiceConsumer { // Called immediately preceding the verification callback invocation. void RemoveInvalidPaymentApps(); + const url::Origin merchant_origin_; DeveloperConsoleLogger log_; // Downloads the manifests. diff --git a/chromium/components/payments/content/payment_app_factory.h b/chromium/components/payments/content/payment_app_factory.h index 28f03466b20..7d74c3a79a3 100644 --- a/chromium/components/payments/content/payment_app_factory.h +++ b/chromium/components/payments/content/payment_app_factory.h @@ -11,7 +11,9 @@ #include "base/callback_forward.h" #include "base/macros.h" #include "base/memory/weak_ptr.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" class GURL; @@ -21,6 +23,7 @@ class AutofillProfile; } // namespace autofill namespace content { +class RenderFrameHost; class WebContents; } // namespace content @@ -30,8 +33,9 @@ class Origin; namespace payments { -class PaymentRequestSpec; class ContentPaymentRequestDelegate; +class PaymentManifestWebDataService; +class PaymentRequestSpec; // Base class for a factory that can create instances of payment apps. class PaymentAppFactory { @@ -41,14 +45,23 @@ class PaymentAppFactory { virtual ~Delegate() = default; virtual content::WebContents* GetWebContents() = 0; - virtual ContentPaymentRequestDelegate* GetPaymentRequestDelegate() = 0; - virtual PaymentRequestSpec* GetSpec() = 0; virtual const GURL& GetTopOrigin() = 0; virtual const GURL& GetFrameOrigin() = 0; + virtual const url::Origin& GetFrameSecurityOrigin() = 0; + virtual content::RenderFrameHost* GetInitiatorRenderFrameHost() const = 0; + virtual const std::vector<mojom::PaymentMethodDataPtr>& GetMethodData() + const = 0; + virtual scoped_refptr<PaymentManifestWebDataService> + GetPaymentManifestWebDataService() const = 0; + virtual bool MayCrawlForInstallablePaymentApps() = 0; + + // These parameters are only used to create native payment apps. virtual const std::vector<autofill::AutofillProfile*>& GetBillingProfiles() = 0; virtual bool IsRequestedAutofillDataAvailable() = 0; - virtual bool MayCrawlForInstallablePaymentApps() = 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. @@ -63,6 +76,18 @@ class PaymentAppFactory { virtual void OnPaymentAppCreationError( const std::string& error_message) = 0; + // 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. + virtual bool SkipCreatingNativePaymentApps() const = 0; + + // When SkipCreatingNativePaymentApps() is true, this callback is called + // when service-worker payment app info is available. + virtual void OnCreatingNativePaymentAppsSkipped( + const content::PaymentAppProvider::PaymentApps& apps, + const 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 224077573ef..b0631dd395b 100644 --- a/chromium/components/payments/content/payment_app_service.cc +++ b/chromium/components/payments/content/payment_app_service.cc @@ -21,10 +21,12 @@ PaymentAppService::PaymentAppService() { PaymentAppService::~PaymentAppService() = default; +size_t PaymentAppService::GetNumberOfFactories() const { + return factories_.size(); +} + void PaymentAppService::Create( - base::WeakPtr<PaymentAppFactory::Delegate> delegate, - size_t* number_of_payment_app_factories) { - *number_of_payment_app_factories = factories_.size(); + base::WeakPtr<PaymentAppFactory::Delegate> delegate) { for (const auto& factory : factories_) { factory->Create(delegate); } diff --git a/chromium/components/payments/content/payment_app_service.h b/chromium/components/payments/content/payment_app_service.h index 2a62d3f0745..cea8f09d206 100644 --- a/chromium/components/payments/content/payment_app_service.h +++ b/chromium/components/payments/content/payment_app_service.h @@ -22,14 +22,13 @@ class PaymentAppService : public KeyedService { PaymentAppService(); ~PaymentAppService() override; - // Sets |number_of_payment_app_factories| to the number of payment app - // factories, which is the number of times that - // |delegate->OnDoneCreatingPaymentApps()| will be called. The value is passed - // in as out-param instead of being returned, so it can be set before any - // factory can call OnDoneCreatingPaymentApps(), which can happen for - // factories that execute synchronously, e.g., AutofillPaymentAppFactory. - void Create(base::WeakPtr<PaymentAppFactory::Delegate> delegate, - size_t* number_of_payment_app_factories); + // Returns the number of payment app factories, which is the number of times + // that |delegate->OnDoneCreatingPaymentApps()| will be called as a result of + // Create(). + size_t GetNumberOfFactories() const; + + // Create payment apps for |delegate|. + void Create(base::WeakPtr<PaymentAppFactory::Delegate> delegate); 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 2edd15570d7..5d982a6f4ff 100644 --- a/chromium/components/payments/content/payment_app_unittest.cc +++ b/chromium/components/payments/content/payment_app_unittest.cc @@ -7,15 +7,20 @@ #include <vector> #include "base/strings/utf_string_conversions.h" +#include "base/test/scoped_feature_list.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/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" #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" @@ -48,6 +53,8 @@ class PaymentAppTest : public testing::TestWithParam<RequiredPaymentOptions>, required_options_(GetParam()) { local_card_.set_billing_address_id(address_.guid()); CreateSpec(); + web_contents_ = + test_web_contents_factory_.CreateWebContents(&browser_context_); } std::unique_ptr<ServiceWorkerPaymentApp> CreateServiceWorkerPaymentApp( @@ -56,8 +63,6 @@ class PaymentAppTest : public testing::TestWithParam<RequiredPaymentOptions>, bool handles_name, bool handles_phone, bool handles_email) { - constexpr int kBitmapDimension = 16; - std::unique_ptr<content::StoredPaymentApp> stored_app = std::make_unique<content::StoredPaymentApp>(); stored_app->registration_id = 123456; @@ -65,8 +70,7 @@ class PaymentAppTest : public testing::TestWithParam<RequiredPaymentOptions>, stored_app->name = "bobpay"; stored_app->icon = std::make_unique<SkBitmap>(); if (can_preselect) { - stored_app->icon->allocN32Pixels(kBitmapDimension, kBitmapDimension); - stored_app->icon->eraseColor(SK_ColorRED); + PopulateIcon(stored_app->icon.get()); } if (handles_shipping) { stored_app->supported_delegations.shipping_address = true; @@ -86,8 +90,45 @@ class PaymentAppTest : public testing::TestWithParam<RequiredPaymentOptions>, GURL("https://testmerchant.com/bobpay"), spec_.get(), std::move(stored_app), &delegate_, /*identity_callback=*/ - base::Bind([](const url::Origin&, - int64_t) { /* Intentionally left blank. */ })); + base::BindRepeating([](const url::Origin&, + int64_t) { /* Intentionally left blank. */ })); + } + + std::unique_ptr<ServiceWorkerPaymentApp> + CreateInstallableServiceWorkerPaymentApp(bool can_preselect, + bool handles_shipping, + bool handles_name, + bool handles_phone, + bool handles_email) { + auto installable_app = std::make_unique<WebAppInstallationInfo>(); + installable_app->name = "installable_pay"; + installable_app->sw_js_url = "https://pay.example/app.js"; + installable_app->sw_scope = "https://pay.example"; + installable_app->icon = std::make_unique<SkBitmap>(); + if (can_preselect) + PopulateIcon(installable_app->icon.get()); + if (handles_shipping) + installable_app->supported_delegations.shipping_address = true; + if (handles_name) + installable_app->supported_delegations.payer_name = true; + if (handles_phone) + installable_app->supported_delegations.payer_phone = true; + if (handles_email) + installable_app->supported_delegations.payer_email = true; + + 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. */ })); + } + + static void PopulateIcon(SkBitmap* icon) { + constexpr int kBitmapDimension = 16; + icon->allocN32Pixels(kBitmapDimension, kBitmapDimension); + icon->eraseColor(SK_ColorRED); } autofill::CreditCard& local_credit_card() { return local_card_; } @@ -132,6 +173,8 @@ class PaymentAppTest : public testing::TestWithParam<RequiredPaymentOptions>, content::BrowserTaskEnvironment task_environment_; content::TestBrowserContext browser_context_; + content::TestWebContentsFactory test_web_contents_factory_; + content::WebContents* web_contents_; autofill::AutofillProfile address_; autofill::CreditCard local_card_; std::vector<autofill::AutofillProfile*> billing_profiles_; @@ -154,27 +197,17 @@ INSTANTIATE_TEST_SUITE_P( TEST_P(PaymentAppTest, SortApps) { std::vector<PaymentApp*> apps; - // Add a complete app with mismatching type. - autofill::CreditCard complete_dismatching_card = local_credit_card(); - AutofillPaymentApp complete_dismatching_cc_app( - "visa", complete_dismatching_card, - /*matches_merchant_card_type_exactly=*/false, billing_profiles(), "en-US", - nullptr); - apps.push_back(&complete_dismatching_cc_app); - - // Add an app with no billing address. + // Add a card with no billing address. autofill::CreditCard card_with_no_address = local_credit_card(); card_with_no_address.set_billing_address_id(""); AutofillPaymentApp cc_app_with_no_address( - "visa", card_with_no_address, /*matches_merchant_card_type_exactly=*/true, - billing_profiles(), "en-US", nullptr); + "visa", card_with_no_address, billing_profiles(), "en-US", nullptr); apps.push_back(&cc_app_with_no_address); - // Add an expired app. + // Add an expired card. autofill::CreditCard expired_card = local_credit_card(); expired_card.SetExpirationYear(2016); AutofillPaymentApp expired_cc_app("visa", expired_card, - /*matches_merchant_card_type_exactly=*/true, billing_profiles(), "en-US", nullptr); apps.push_back(&expired_cc_app); @@ -194,40 +227,35 @@ TEST_P(PaymentAppTest, SortApps) { false /* = handles_email */); apps.push_back(preselectable_sw_app.get()); - // Add an app with no name. + // Add a card with no name. autofill::CreditCard card_with_no_name = local_credit_card(); card_with_no_name.SetInfo( autofill::AutofillType(autofill::CREDIT_CARD_NAME_FULL), base::ASCIIToUTF16(""), "en-US"); - AutofillPaymentApp cc_app_with_no_name( - "visa", card_with_no_name, /*matches_merchant_card_type_exactly=*/true, - billing_profiles(), "en-US", nullptr); + AutofillPaymentApp cc_app_with_no_name("visa", card_with_no_name, + billing_profiles(), "en-US", nullptr); apps.push_back(&cc_app_with_no_name); - // Add a complete matching app. - autofill::CreditCard complete_matching_card = local_credit_card(); - AutofillPaymentApp complete_matching_cc_app( - "visa", complete_matching_card, - /*matches_merchant_card_type_exactly=*/true, billing_profiles(), "en-US", - nullptr); - apps.push_back(&complete_matching_cc_app); + // Add a complete card. + autofill::CreditCard complete_card = local_credit_card(); + AutofillPaymentApp complete_cc_app("visa", complete_card, billing_profiles(), + "en-US", nullptr); + apps.push_back(&complete_cc_app); - // Add an app with no number. + // Add a card with no number. autofill::CreditCard card_with_no_number = local_credit_card(); card_with_no_number.SetNumber(base::ASCIIToUTF16("")); AutofillPaymentApp cc_app_with_no_number( - "visa", card_with_no_number, /*matches_merchant_card_type_exactly=*/true, - billing_profiles(), "en-US", nullptr); + "visa", card_with_no_number, billing_profiles(), "en-US", nullptr); apps.push_back(&cc_app_with_no_number); - // Add a complete matching app that is most frequently used. + // Add a complete matching card that is most frequently used. autofill::CreditCard complete_frequently_used_card = local_credit_card(); AutofillPaymentApp complete_frequently_used_cc_app( - "visa", complete_frequently_used_card, - /*matches_merchant_card_type_exactly=*/true, billing_profiles(), "en-US", + "visa", complete_frequently_used_card, billing_profiles(), "en-US", nullptr); apps.push_back(&complete_frequently_used_cc_app); - // Record use of this app. + // Record use of this card. complete_frequently_used_cc_app.credit_card()->RecordAndLogUse(); // Sort the apps and validate the new order. @@ -238,8 +266,7 @@ TEST_P(PaymentAppTest, SortApps) { // Autfill apps (credit cards) come after sw apps. EXPECT_EQ(apps[i++], &complete_frequently_used_cc_app); - EXPECT_EQ(apps[i++], &complete_matching_cc_app); - EXPECT_EQ(apps[i++], &complete_dismatching_cc_app); + EXPECT_EQ(apps[i++], &complete_cc_app); EXPECT_EQ(apps[i++], &expired_cc_app); EXPECT_EQ(apps[i++], &cc_app_with_no_name); EXPECT_EQ(apps[i++], &cc_app_with_no_address); @@ -347,4 +374,98 @@ TEST_P(PaymentAppTest, SortAppsBasedOnSupportedDelegations) { } } +TEST_P(PaymentAppTest, SortApps_DownRankJustInTimePaymentApp) { + base::test::ScopedFeatureList feature_list; + feature_list.InitAndEnableFeature(features::kDownRankJustInTimePaymentApp); + + std::vector<PaymentApp*> apps; + + // Add a card with no billing address. + autofill::CreditCard card_with_no_address = local_credit_card(); + card_with_no_address.set_billing_address_id(""); + AutofillPaymentApp cc_app_with_no_address( + "visa", card_with_no_address, billing_profiles(), "en-US", nullptr); + apps.push_back(&cc_app_with_no_address); + + // 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); + apps.push_back(&expired_cc_app); + + // Add a card with no number. + autofill::CreditCard card_with_no_number = local_credit_card(); + card_with_no_number.SetNumber(base::ASCIIToUTF16("")); + AutofillPaymentApp cc_app_with_no_number( + "visa", card_with_no_number, billing_profiles(), "en-US", nullptr); + apps.push_back(&cc_app_with_no_number); + + // Add a card with no name. + autofill::CreditCard card_with_no_name = local_credit_card(); + card_with_no_name.SetInfo( + autofill::AutofillType(autofill::CREDIT_CARD_NAME_FULL), + base::ASCIIToUTF16(""), "en-US"); + AutofillPaymentApp cc_app_with_no_name("visa", card_with_no_name, + billing_profiles(), "en-US", nullptr); + apps.push_back(&cc_app_with_no_name); + + // Add a just-in-time installable sw based payment app. + std::unique_ptr<ServiceWorkerPaymentApp> installable_sw_app = + CreateInstallableServiceWorkerPaymentApp( + true /* = can_preselect */, false /* = handles_shipping */, + false /* = handles_name */, false /* = handles_phone */, + false /* = handles_email */); + apps.push_back(installable_sw_app.get()); + + // Add an installed, non-preselectable sw based payment app. + std::unique_ptr<ServiceWorkerPaymentApp> non_preselectable_sw_app = + CreateServiceWorkerPaymentApp( + false /* = can_preselect */, false /* = handles_shipping */, + false /* = handles_name */, false /* = handles_phone */, + false /* = handles_email */); + apps.push_back(non_preselectable_sw_app.get()); + + // Add an installed, preselectable, sw based payment app. + std::unique_ptr<ServiceWorkerPaymentApp> preselectable_sw_app = + CreateServiceWorkerPaymentApp( + true /* = can_preselect */, false /* = handles_shipping */, + false /* = handles_name */, false /* = handles_phone */, + false /* = handles_email */); + apps.push_back(preselectable_sw_app.get()); + + // Add a complete card. + autofill::CreditCard complete_card = local_credit_card(); + AutofillPaymentApp complete_cc_app("visa", complete_card, billing_profiles(), + "en-US", nullptr); + apps.push_back(&complete_cc_app); + + // Add a complete matching card that is most frequently used. + autofill::CreditCard complete_frequently_used_card = local_credit_card(); + AutofillPaymentApp complete_frequently_used_cc_app( + "visa", complete_frequently_used_card, billing_profiles(), "en-US", + nullptr); + apps.push_back(&complete_frequently_used_cc_app); + // Record use of this card. + complete_frequently_used_cc_app.credit_card()->RecordAndLogUse(); + + // Sort the apps and validate the new order. + PaymentApp::SortApps(&apps); + size_t i = 0; + + // Installed sw based payment handlers come first. + EXPECT_EQ(apps[i++], preselectable_sw_app.get()); + EXPECT_EQ(apps[i++], non_preselectable_sw_app.get()); + // Complete autofill apps are sorted by frecency. + EXPECT_EQ(apps[i++], &complete_frequently_used_cc_app); + EXPECT_EQ(apps[i++], &complete_cc_app); + EXPECT_EQ(apps[i++], &expired_cc_app); + // Just-in-time installable sw based payment apps come after autofill apps. + EXPECT_EQ(apps[i++], installable_sw_app.get()); + // Incomplete autofill apps (credit cards) come last. + EXPECT_EQ(apps[i++], &cc_app_with_no_name); + EXPECT_EQ(apps[i++], &cc_app_with_no_address); + EXPECT_EQ(apps[i++], &cc_app_with_no_number); +} + } // namespace payments diff --git a/chromium/components/payments/content/payment_event_response_util.cc b/chromium/components/payments/content/payment_event_response_util.cc index 3742ae3d946..4ace52f9ba1 100644 --- a/chromium/components/payments/content/payment_event_response_util.cc +++ b/chromium/components/payments/content/payment_event_response_util.cc @@ -10,6 +10,45 @@ namespace payments { +base::StringPiece ConvertCanMakePaymentEventResponseTypeToErrorString( + mojom::CanMakePaymentEventResponseType response_type) { + switch (response_type) { + case mojom::CanMakePaymentEventResponseType::BOOLEAN_CONVERSION_ERROR: + return errors::kCanMakePaymentEventBooleanConversionError; + case mojom::CanMakePaymentEventResponseType::BROWSER_ERROR: + return errors::kCanMakePaymentEventBrowserError; + case mojom::CanMakePaymentEventResponseType::INTERNAL_ERROR: + return errors::kCanMakePaymentEventInternalError; + case mojom::CanMakePaymentEventResponseType::INVALID_ACCOUNT_BALANCE_VALUE: + return errors::kCanMakePaymentEventInvalidAccountBalanceValue; + case mojom::CanMakePaymentEventResponseType:: + MINIMAL_UI_RESPONSE_CONVERSION_ERROR: + return errors::kCanMakePaymentEventMinimalUiResponseConversionError; + case mojom::CanMakePaymentEventResponseType::NO_ACCOUNT_BALANCE_VALUE: + return errors::kCanMakePaymentEventNoAccountBalanceValue; + case mojom::CanMakePaymentEventResponseType::NO_CAN_MAKE_PAYMENT_VALUE: + return errors::kCanMakePaymentEventNoCanMakePaymentValue; + case mojom::CanMakePaymentEventResponseType::NO_EXPLICITLY_VERIFIED_METHODS: + return errors::kCanMakePaymentEventNoExplicitlyVerifiedMethods; + case mojom::CanMakePaymentEventResponseType::NO_READY_FOR_MINIMAL_UI_VALUE: + return errors::kCanMakePaymentEventNoReadyForMinimalUiValue; + case mojom::CanMakePaymentEventResponseType::NO_RESPONSE: + return errors::kCanMakePaymentEventNoResponse; + case mojom::CanMakePaymentEventResponseType::NOT_INSTALLED: + return errors::kCanMakePaymentEventNotInstalled; + case mojom::CanMakePaymentEventResponseType::NO_URL_BASED_PAYMENT_METHODS: + return errors::kCanMakePaymentEventNoUrlBasedPaymentMethods; + case mojom::CanMakePaymentEventResponseType::REJECT: + return errors::kCanMakePaymentEventRejected; + case mojom::CanMakePaymentEventResponseType::TIMEOUT: + return errors::kCanMakePaymentEventTimeout; + case mojom::CanMakePaymentEventResponseType::INCOGNITO: + // Intentionally fallthrough. + case mojom::CanMakePaymentEventResponseType::SUCCESS: + return ""; + } +} + base::StringPiece ConvertPaymentEventResponseTypeToErrorString( mojom::PaymentEventResponseType response_type) { switch (response_type) { diff --git a/chromium/components/payments/content/payment_event_response_util.h b/chromium/components/payments/content/payment_event_response_util.h index afa0c53dfc5..7ec6a20e2a0 100644 --- a/chromium/components/payments/content/payment_event_response_util.h +++ b/chromium/components/payments/content/payment_event_response_util.h @@ -10,6 +10,11 @@ namespace payments { +// Converts the given 'canmakepayment' event |response_type| into a +// developer-facing error string. SUCCESS is converted into an empty string. +base::StringPiece ConvertCanMakePaymentEventResponseTypeToErrorString( + mojom::CanMakePaymentEventResponseType response_type); + // Converts the given 'paymentrequest' event |response_type| into a // developer-facing error string. PAYMENT_EVENT_SUCCESS is converted into an // empty string. diff --git a/chromium/components/payments/content/payment_manifest_web_data_service.cc b/chromium/components/payments/content/payment_manifest_web_data_service.cc index 1cd0d2d02ed..8294b9eaac2 100644 --- a/chromium/components/payments/content/payment_manifest_web_data_service.cc +++ b/chromium/components/payments/content/payment_manifest_web_data_service.cc @@ -15,9 +15,8 @@ namespace payments { PaymentManifestWebDataService::PaymentManifestWebDataService( scoped_refptr<WebDatabaseService> wdbs, - const ProfileErrorCallback& callback, - const scoped_refptr<base::SingleThreadTaskRunner>& ui_task_runner) - : WebDataServiceBase(wdbs, callback, ui_task_runner) {} + scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) + : WebDataServiceBase(std::move(wdbs), std::move(ui_task_runner)) {} PaymentManifestWebDataService::~PaymentManifestWebDataService() {} @@ -25,8 +24,9 @@ void PaymentManifestWebDataService::AddPaymentWebAppManifest( std::vector<WebAppManifestSection> manifest) { wdbs_->ScheduleDBTask( FROM_HERE, - base::Bind(&PaymentManifestWebDataService::AddPaymentWebAppManifestImpl, - this, std::move(manifest))); + base::BindOnce( + &PaymentManifestWebDataService::AddPaymentWebAppManifestImpl, this, + std::move(manifest))); } WebDatabase::State PaymentManifestWebDataService::AddPaymentWebAppManifestImpl( @@ -45,8 +45,9 @@ void PaymentManifestWebDataService::AddPaymentMethodManifest( std::vector<std::string> app_package_names) { wdbs_->ScheduleDBTask( FROM_HERE, - base::Bind(&PaymentManifestWebDataService::AddPaymentMethodManifestImpl, - this, payment_method, std::move(app_package_names))); + base::BindOnce( + &PaymentManifestWebDataService::AddPaymentMethodManifestImpl, this, + payment_method, std::move(app_package_names))); } WebDatabase::State PaymentManifestWebDataService::AddPaymentMethodManifestImpl( @@ -67,8 +68,9 @@ PaymentManifestWebDataService::GetPaymentWebAppManifest( WebDataServiceConsumer* consumer) { return wdbs_->ScheduleDBTaskWithResult( FROM_HERE, - base::Bind(&PaymentManifestWebDataService::GetPaymentWebAppManifestImpl, - this, web_app), + base::BindOnce( + &PaymentManifestWebDataService::GetPaymentWebAppManifestImpl, this, + web_app), consumer); } @@ -89,8 +91,9 @@ PaymentManifestWebDataService::GetPaymentMethodManifest( WebDataServiceConsumer* consumer) { return wdbs_->ScheduleDBTaskWithResult( FROM_HERE, - base::Bind(&PaymentManifestWebDataService::GetPaymentMethodManifestImpl, - this, payment_method), + base::BindOnce( + &PaymentManifestWebDataService::GetPaymentMethodManifestImpl, this, + payment_method), consumer); } @@ -110,4 +113,4 @@ void PaymentManifestWebDataService::RemoveExpiredData(WebDatabase* db) { WebAppManifestSectionTable::FromWebDatabase(db)->RemoveExpiredData(); } -} // namespace payments
\ No newline at end of file +} // namespace payments diff --git a/chromium/components/payments/content/payment_manifest_web_data_service.h b/chromium/components/payments/content/payment_manifest_web_data_service.h index a57a7645b1e..e81845e9cb3 100644 --- a/chromium/components/payments/content/payment_manifest_web_data_service.h +++ b/chromium/components/payments/content/payment_manifest_web_data_service.h @@ -29,8 +29,7 @@ class PaymentManifestWebDataService : public WebDataServiceBase { public: PaymentManifestWebDataService( scoped_refptr<WebDatabaseService> wdbs, - const ProfileErrorCallback& callback, - const scoped_refptr<base::SingleThreadTaskRunner>& ui_task_runner); + scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner); // Adds the web app |manifest|. void AddPaymentWebAppManifest(std::vector<WebAppManifestSection> manifest); diff --git a/chromium/components/payments/content/payment_request.cc b/chromium/components/payments/content/payment_request.cc index d8eab2d1733..dd5ee3b26fd 100644 --- a/chromium/components/payments/content/payment_request.cc +++ b/chromium/components/payments/content/payment_request.cc @@ -18,6 +18,7 @@ #include "components/payments/content/payment_request_converter.h" #include "components/payments/content/payment_request_web_contents_manager.h" #include "components/payments/core/can_make_payment_query.h" +#include "components/payments/core/error_message_util.h" #include "components/payments/core/error_strings.h" #include "components/payments/core/features.h" #include "components/payments/core/method_strings.h" @@ -34,9 +35,11 @@ #include "components/url_formatter/elide_url.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/render_frame_host.h" +#include "content/public/browser/render_process_host.h" #include "content/public/browser/web_contents.h" #include "content/public/common/content_features.h" #include "content/public/common/origin_util.h" +#include "services/metrics/public/cpp/ukm_source_id.h" namespace payments { namespace { @@ -49,27 +52,6 @@ bool IsGooglePaymentMethod(const std::string& method_name) { method_name == methods::kAndroidPay; } -std::string GetNotSupportedErrorMessage(PaymentRequestSpec* spec) { - if (!spec || spec->payment_method_identifiers_set().empty()) - return errors::kGenericPaymentMethodNotSupportedMessage; - - std::vector<std::string> method_names( - spec->payment_method_identifiers_set().size()); - std::transform( - spec->payment_method_identifiers_set().begin(), - spec->payment_method_identifiers_set().end(), method_names.begin(), - [](const std::string& method_name) { return "\"" + method_name + "\""; }); - - std::string output; - bool replaced = base::ReplaceChars( - method_names.size() == 1 - ? errors::kSinglePaymentMethodNotSupportedFormat - : errors::kMultiplePaymentMethodsNotSupportedFormat, - "$", base::JoinString(method_names, ", "), &output); - DCHECK(replaced); - return output; -} - // Redact shipping address before exposing it in ShippingAddressChangeEvent. // https://w3c.github.io/payment-request/#shipping-address-changed-algorithm mojom::PaymentAddressPtr RedactShippingAddress( @@ -97,6 +79,11 @@ PaymentRequest::PaymentRequest( mojo::PendingReceiver<mojom::PaymentRequest> receiver, ObserverForTest* observer_for_testing) : web_contents_(web_contents), + // TODO(crbug.com/1058840): change to WeakPtr<RenderFrameHost> once + // RenderFrameHost provides a WeakPtr API. + initiator_frame_routing_id_(content::GlobalFrameRoutingId( + render_frame_host->GetProcess()->GetID(), + render_frame_host->GetRoutingID())), log_(web_contents_), delegate_(std::move(delegate)), manager_(manager), @@ -107,6 +94,7 @@ PaymentRequest::PaymentRequest( web_contents_->GetLastCommittedURL())), frame_origin_(url_formatter::FormatUrlForSecurityDisplay( render_frame_host->GetLastCommittedURL())), + frame_security_origin_(render_frame_host->GetLastCommittedOrigin()), observer_for_testing_(observer_for_testing), journey_logger_(delegate_->IsIncognito(), ukm::GetSourceIdForWebContentsDocument(web_contents)) { @@ -181,11 +169,22 @@ void PaymentRequest::Init( return; } + // TODO(crbug.com/1058840): change to WeakPtr<RenderFrameHost> once + // RenderFrameHost provides a WeakPtr API. + content::RenderFrameHost* initiator_frame = + content::RenderFrameHost::FromID(initiator_frame_routing_id_); + if (!initiator_frame) { + log_.Error(errors::kInvalidInitiatorFrame); + OnConnectionTerminated(); + return; + } + spec_ = std::make_unique<PaymentRequestSpec>( std::move(options), std::move(details), std::move(method_data), /*observer=*/this, delegate_->GetApplicationLocale()); state_ = std::make_unique<PaymentRequestState>( - web_contents_, top_level_origin_, frame_origin_, spec_.get(), + 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, @@ -422,6 +421,9 @@ void PaymentRequest::Abort() { if (observer_for_testing_) observer_for_testing_->OnAbortCalled(); + + if (accepting_abort) + state_->OnAbort(); } void PaymentRequest::Complete(mojom::PaymentComplete result) { @@ -574,7 +576,9 @@ void PaymentRequest::AreRequestedMethodsSupportedCallback( journey_logger_.SetNotShown( JourneyLogger::NOT_SHOWN_REASON_NO_SUPPORTED_PAYMENT_METHOD); client_->OnError(mojom::PaymentErrorReason::NOT_SUPPORTED, - GetNotSupportedErrorMessage(spec_.get()) + + GetNotSupportedErrorMessage( + spec_ ? spec_->payment_method_identifiers_set() + : std::set<std::string>()) + (error_message.empty() ? "" : " " + error_message)); if (observer_for_testing_) observer_for_testing_->OnNotSupportedError(); @@ -776,6 +780,14 @@ bool PaymentRequest::IsIncognito() const { return delegate_->IsIncognito(); } +void PaymentRequest::OnPaymentHandlerOpenWindowCalled() { + DCHECK(state_->selected_app()); + // UKM for payment app origin should get recorded only when the origin of the + // invoked payment app is shown to the user. + journey_logger_.SetPaymentAppUkmSourceId( + state_->selected_app()->UkmSourceId()); +} + void PaymentRequest::RecordFirstAbortReason( JourneyLogger::AbortReason abort_reason) { if (!has_recorded_completion_) { diff --git a/chromium/components/payments/content/payment_request.h b/chromium/components/payments/content/payment_request.h index 5c77ab5d804..c7ca2024fa3 100644 --- a/chromium/components/payments/content/payment_request.h +++ b/chromium/components/payments/content/payment_request.h @@ -17,10 +17,12 @@ #include "components/payments/content/payment_request_state.h" #include "components/payments/content/service_worker_payment_app.h" #include "components/payments/core/journey_logger.h" +#include "content/public/browser/global_routing_id.h" #include "mojo/public/cpp/bindings/receiver.h" #include "mojo/public/cpp/bindings/remote.h" #include "third_party/blink/public/mojom/payments/payment_request.mojom.h" #include "url/gurl.h" +#include "url/origin.h" namespace content { class RenderFrameHost; @@ -125,6 +127,9 @@ class PaymentRequest : public mojom::PaymentRequest, bool IsIncognito() const; + // Called when the payment handler requests to open a payment handler window. + void OnPaymentHandlerOpenWindowCalled(); + content::WebContents* web_contents() { return web_contents_; } bool skipped_payment_request_ui() { return skipped_payment_request_ui_; } @@ -183,6 +188,7 @@ class PaymentRequest : public mojom::PaymentRequest, bool warn_localhost_or_file); content::WebContents* web_contents_; + const content::GlobalFrameRoutingId initiator_frame_routing_id_; DeveloperConsoleLogger log_; std::unique_ptr<ContentPaymentRequestDelegate> delegate_; // |manager_| owns this PaymentRequest. @@ -199,14 +205,22 @@ class PaymentRequest : public mojom::PaymentRequest, // browser process. PaymentHandlerHost payment_handler_host_; - // The RFC 6454 origin of the top level frame that has invoked PaymentRequest - // API. This is what the user sees in the address bar. + // The scheme, host, and port of the top level frame that has invoked + // PaymentRequest API as formatted by + // url_formatter::FormatUrlForSecurityDisplay(). This is what the user sees in + // the address bar. const GURL top_level_origin_; - // The RFC 6454 origin of the frame that has invoked PaymentRequest API. This - // can be either the main frame or an iframe. + // The scheme, host, and port of the frame that has invoked PaymentRequest API + // as formatted by url_formatter::FormatUrlForSecurityDisplay(). This can be + // either the main frame or an iframe. const GURL frame_origin_; + // The security origin of the frame that has invoked PaymentRequest API. This + // can be opaque. Used by security features like 'Sec-Fetch-Site' and + // 'Cross-Origin-Resource-Policy'. + const url::Origin frame_security_origin_; + // May be null, must outlive this object. ObserverForTest* observer_for_testing_; diff --git a/chromium/components/payments/content/payment_request_converter.cc b/chromium/components/payments/content/payment_request_converter.cc index 8a1abc13c1f..d11a982dd5a 100644 --- a/chromium/components/payments/content/payment_request_converter.cc +++ b/chromium/components/payments/content/payment_request_converter.cc @@ -57,20 +57,6 @@ PaymentShippingOption ConvertPaymentShippingOption( } // namespace -autofill::CreditCard::CardType GetBasicCardType( - const mojom::BasicCardType& type) { - switch (type) { - case mojom::BasicCardType::CREDIT: - return autofill::CreditCard::CARD_TYPE_CREDIT; - case mojom::BasicCardType::DEBIT: - return autofill::CreditCard::CARD_TYPE_DEBIT; - case mojom::BasicCardType::PREPAID: - return autofill::CreditCard::CARD_TYPE_PREPAID; - } - NOTREACHED(); - return autofill::CreditCard::CARD_TYPE_UNKNOWN; -} - std::string GetBasicCardNetworkName(const mojom::BasicCardNetwork& network) { switch (network) { case mojom::BasicCardNetwork::AMEX: @@ -105,10 +91,6 @@ PaymentMethodData ConvertPaymentMethodData( method_data_entry->supported_networks) { method_data.supported_networks.push_back(GetBasicCardNetworkName(network)); } - for (const mojom::BasicCardType& type : method_data_entry->supported_types) { - autofill::CreditCard::CardType card_type = GetBasicCardType(type); - method_data.supported_types.insert(card_type); - } return method_data; } diff --git a/chromium/components/payments/content/payment_request_converter.h b/chromium/components/payments/content/payment_request_converter.h index 3ad65c96d46..c953e9e9670 100644 --- a/chromium/components/payments/content/payment_request_converter.h +++ b/chromium/components/payments/content/payment_request_converter.h @@ -15,10 +15,6 @@ namespace payments { class PaymentDetails; class PaymentMethodData; -// Returns the card type associated with the given BasicCardType. -autofill::CreditCard::CardType GetBasicCardType( - const mojom::BasicCardType& type); - // Returns the card network name associated with a given BasicCardNetwork. Names // are inspired by https://www.w3.org/Payments/card-network-ids. std::string GetBasicCardNetworkName(const mojom::BasicCardNetwork& network); diff --git a/chromium/components/payments/content/payment_request_spec.cc b/chromium/components/payments/content/payment_request_spec.cc index 31fe5c1792a..357311240bd 100644 --- a/chromium/components/payments/content/payment_request_spec.cc +++ b/chromium/components/payments/content/payment_request_spec.cc @@ -31,7 +31,6 @@ void PopulateValidatedMethodData( std::vector<std::string>* supported_card_networks, std::set<std::string>* basic_card_specified_networks, std::set<std::string>* supported_card_networks_set, - std::set<autofill::CreditCard::CardType>* supported_card_types_set, std::vector<GURL>* url_payment_method_identifiers, std::set<std::string>* payment_method_identifiers_set, std::map<std::string, std::set<std::string>>* stringified_method_data) { @@ -41,9 +40,6 @@ void PopulateValidatedMethodData( payment_method_identifiers_set); supported_card_networks_set->insert(supported_card_networks->begin(), supported_card_networks->end()); - - data_util::ParseSupportedCardTypes(method_data_vector, - supported_card_types_set); } void PopulateValidatedMethodData( @@ -51,7 +47,6 @@ void PopulateValidatedMethodData( std::vector<std::string>* supported_card_networks, std::set<std::string>* basic_card_specified_networks, std::set<std::string>* supported_card_networks_set, - std::set<autofill::CreditCard::CardType>* supported_card_types_set, std::vector<GURL>* url_payment_method_identifiers, std::set<std::string>* payment_method_identifiers_set, std::map<std::string, std::set<std::string>>* stringified_method_data) { @@ -68,8 +63,8 @@ void PopulateValidatedMethodData( PopulateValidatedMethodData( method_data_vector, supported_card_networks, basic_card_specified_networks, supported_card_networks_set, - supported_card_types_set, url_payment_method_identifiers, - payment_method_identifiers_set, stringified_method_data); + url_payment_method_identifiers, payment_method_identifiers_set, + stringified_method_data); } std::string ToString(bool value) { @@ -101,9 +96,8 @@ PaymentRequestSpec::PaymentRequestSpec( UpdateSelectedShippingOption(/*after_update=*/false); PopulateValidatedMethodData( method_data_, &supported_card_networks_, &basic_card_specified_networks_, - &supported_card_networks_set_, &supported_card_types_set_, - &url_payment_method_identifiers_, &payment_method_identifiers_set_, - &stringified_method_data_); + &supported_card_networks_set_, &url_payment_method_identifiers_, + &payment_method_identifiers_set_, &stringified_method_data_); query_for_quota_ = stringified_method_data_; if (base::Contains(payment_method_identifiers_set_, methods::kBasicCard) && @@ -372,7 +366,6 @@ PaymentRequestSpec::GetApplicableModifier(PaymentApp* selected_app) const { DCHECK(details_->modifiers); for (const auto& modifier : *details_->modifiers) { std::set<std::string> supported_card_networks_set; - std::set<autofill::CreditCard::CardType> supported_types; // The following 4 are unused but required by PopulateValidatedMethodData. std::set<std::string> basic_card_specified_networks; std::vector<std::string> supported_networks; @@ -382,14 +375,13 @@ PaymentRequestSpec::GetApplicableModifier(PaymentApp* selected_app) const { PopulateValidatedMethodData( {ConvertPaymentMethodData(modifier->method_data)}, &supported_networks, &basic_card_specified_networks, &supported_card_networks_set, - &supported_types, &url_payment_method_identifiers, - &payment_method_identifiers_set, &stringified_method_data); + &url_payment_method_identifiers, &payment_method_identifiers_set, + &stringified_method_data); if (selected_app->IsValidForModifier( modifier->method_data->supported_method, !modifier->method_data->supported_networks.empty(), - supported_card_networks_set, - !modifier->method_data->supported_types.empty(), supported_types)) { + supported_card_networks_set)) { return &modifier; } } diff --git a/chromium/components/payments/content/payment_request_spec.h b/chromium/components/payments/content/payment_request_spec.h index ea28e958006..87af537dadd 100644 --- a/chromium/components/payments/content/payment_request_spec.h +++ b/chromium/components/payments/content/payment_request_spec.h @@ -144,10 +144,6 @@ class PaymentRequestSpec : public PaymentOptionsProvider, const { return stringified_method_data_; } - const std::set<autofill::CreditCard::CardType>& supported_card_types_set() - const { - return supported_card_types_set_; - } const std::vector<GURL>& url_payment_method_identifiers() const { return url_payment_method_identifiers_; } @@ -247,8 +243,6 @@ class PaymentRequestSpec : public PaymentOptionsProvider, std::vector<std::string> supported_card_networks_; std::set<std::string> supported_card_networks_set_; - std::set<autofill::CreditCard::CardType> supported_card_types_set_; - // Only the set of basic-card specified networks. NOTE: callers should use // |supported_card_networks_set_| to check merchant support. std::set<std::string> basic_card_specified_networks_; diff --git a/chromium/components/payments/content/payment_request_state.cc b/chromium/components/payments/content/payment_request_state.cc index d961d5d17f4..b2ee9bb3e00 100644 --- a/chromium/components/payments/content/payment_request_state.cc +++ b/chromium/components/payments/content/payment_request_state.cc @@ -33,19 +33,12 @@ #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" #include "content/public/common/content_features.h" namespace payments { namespace { -// Checks whether any of the |apps| return true in IsValidForCanMakePayment(). -bool GetHasEnrolledInstrument( - const std::vector<std::unique_ptr<PaymentApp>>& apps) { - return std::any_of(apps.begin(), apps.end(), [](const auto& app) { - return app->IsValidForCanMakePayment(); - }); -} - // Invokes the |callback| with |status|. void CallStatusCallback(PaymentRequestState::StatusCallback callback, bool status) { @@ -64,8 +57,10 @@ void PostStatusCallback(PaymentRequestState::StatusCallback callback, PaymentRequestState::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, @@ -74,8 +69,10 @@ PaymentRequestState::PaymentRequestState( const ServiceWorkerPaymentApp::IdentityCallback& sw_identity_callback, JourneyLogger* journey_logger) : web_contents_(web_contents), + initiator_render_frame_host_(initiator_render_frame_host), top_origin_(top_level_origin), frame_origin_(frame_origin), + frame_security_origin_(frame_security_origin), app_locale_(app_locale), spec_(spec), delegate_(delegate), @@ -89,10 +86,10 @@ PaymentRequestState::PaymentRequestState( PopulateProfileCache(); // |web_contents_| is null in unit tests. - PaymentAppServiceFactory::GetForContext( - web_contents_ ? web_contents_->GetBrowserContext() : nullptr) - ->Create(weak_ptr_factory_.GetWeakPtr(), - &number_of_payment_app_factories_); + PaymentAppService* service = PaymentAppServiceFactory::GetForContext( + web_contents_ ? web_contents_->GetBrowserContext() : nullptr); + number_of_payment_app_factories_ = service->GetNumberOfFactories(); + service->Create(weak_ptr_factory_.GetWeakPtr()); spec_->AddObserver(this); } @@ -103,12 +100,12 @@ content::WebContents* PaymentRequestState::GetWebContents() { return web_contents_; } -ContentPaymentRequestDelegate* -PaymentRequestState::GetPaymentRequestDelegate() { +ContentPaymentRequestDelegate* PaymentRequestState::GetPaymentRequestDelegate() + const { return payment_request_delegate_; } -PaymentRequestSpec* PaymentRequestState::GetSpec() { +PaymentRequestSpec* PaymentRequestState::GetSpec() const { return spec_; } @@ -120,6 +117,25 @@ const GURL& PaymentRequestState::GetFrameOrigin() { return frame_origin_; } +const url::Origin& PaymentRequestState::GetFrameSecurityOrigin() { + return frame_security_origin_; +} + +content::RenderFrameHost* PaymentRequestState::GetInitiatorRenderFrameHost() + const { + return initiator_render_frame_host_; +} + +const std::vector<mojom::PaymentMethodDataPtr>& +PaymentRequestState::GetMethodData() const { + return GetSpec()->method_data(); +} + +scoped_refptr<PaymentManifestWebDataService> +PaymentRequestState::GetPaymentManifestWebDataService() const { + return GetPaymentRequestDelegate()->GetPaymentManifestWebDataService(); +} + const std::vector<autofill::AutofillProfile*>& PaymentRequestState::GetBillingProfiles() { return shipping_profiles_; @@ -160,6 +176,17 @@ void PaymentRequestState::OnPaymentAppCreationError( get_all_payment_apps_error_ = error_message; } +bool PaymentRequestState::SkipCreatingNativePaymentApps() const { + return false; +} + +void PaymentRequestState::OnCreatingNativePaymentAppsSkipped( + const content::PaymentAppProvider::PaymentApps& unused_apps, + const ServiceWorkerPaymentAppFinder::InstallablePaymentApps& + unused_installable_apps) { + NOTREACHED(); +} + void PaymentRequestState::OnDoneCreatingPaymentApps() { DCHECK_NE(0U, number_of_payment_app_factories_); if (--number_of_payment_app_factories_ > 0U) @@ -168,7 +195,9 @@ void PaymentRequestState::OnDoneCreatingPaymentApps() { SetDefaultProfileSelections(); get_all_apps_finished_ = true; - has_enrolled_instrument_ = GetHasEnrolledInstrument(available_apps_); + has_enrolled_instrument_ = + std::any_of(available_apps_.begin(), available_apps_.end(), + [](const auto& app) { return app->HasEnrolledInstrument(); }); are_requested_methods_supported_ |= !available_apps_.empty(); NotifyOnGetAllPaymentAppsFinished(); NotifyInitialized(); @@ -263,6 +292,13 @@ void PaymentRequestState::AreRequestedMethodsSupported( weak_ptr_factory_.GetWeakPtr(), std::move(callback))); } +void PaymentRequestState::OnAbort() { + // Reset supported method callback when the merchant calls abort before + // OnDoneCreatingPaymentApps(). + if (are_requested_methods_supported_callback_) + are_requested_methods_supported_callback_.Reset(); +} + void PaymentRequestState::CheckRequestedMethodsSupported( MethodsSupportedCallback callback) { DCHECK(get_all_apps_finished_); @@ -598,6 +634,7 @@ void PaymentRequestState::SetDefaultProfileSelections() { selected_app_ = nullptr; if (!available_apps_.empty() && available_apps_[0]->CanPreselect()) { selected_app_ = available_apps_[0].get(); + UpdateIsReadyToPayAndNotifyObservers(); } // Record the missing required payment fields when no complete payment diff --git a/chromium/components/payments/content/payment_request_state.h b/chromium/components/payments/content/payment_request_state.h index bab9c1ce1a5..9487b13a19e 100644 --- a/chromium/components/payments/content/payment_request_state.h +++ b/chromium/components/payments/content/payment_request_state.h @@ -24,6 +24,7 @@ #include "content/public/browser/payment_app_provider.h" #include "content/public/browser/web_contents.h" #include "third_party/blink/public/mojom/payments/payment_request.mojom.h" +#include "url/origin.h" namespace autofill { class AutofillProfile; @@ -32,6 +33,10 @@ class PersonalDataManager; class RegionDataLoader; } // namespace autofill +namespace content { +class RenderFrameHost; +} // namespace content + namespace payments { class ContentPaymentRequestDelegate; @@ -111,8 +116,10 @@ class PaymentRequestState : public PaymentAppFactory::Delegate, 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, @@ -124,10 +131,16 @@ class PaymentRequestState : public PaymentAppFactory::Delegate, // PaymentAppFactory::Delegate content::WebContents* GetWebContents() override; - ContentPaymentRequestDelegate* GetPaymentRequestDelegate() override; - PaymentRequestSpec* GetSpec() override; + ContentPaymentRequestDelegate* GetPaymentRequestDelegate() const override; + PaymentRequestSpec* GetSpec() const override; const GURL& GetTopOrigin() override; const GURL& GetFrameOrigin() override; + const url::Origin& GetFrameSecurityOrigin() override; + content::RenderFrameHost* GetInitiatorRenderFrameHost() const override; + const std::vector<mojom::PaymentMethodDataPtr>& GetMethodData() + const override; + scoped_refptr<PaymentManifestWebDataService> + GetPaymentManifestWebDataService() const override; const std::vector<autofill::AutofillProfile*>& GetBillingProfiles() override; bool IsRequestedAutofillDataAvailable() override; bool MayCrawlForInstallablePaymentApps() override; @@ -135,6 +148,11 @@ class PaymentRequestState : public PaymentAppFactory::Delegate, int64_t registration_id) override; void OnPaymentAppCreated(std::unique_ptr<PaymentApp> app) override; void OnPaymentAppCreationError(const std::string& error_message) override; + bool SkipCreatingNativePaymentApps() const override; + void OnCreatingNativePaymentAppsSkipped( + const content::PaymentAppProvider::PaymentApps& apps, + const ServiceWorkerPaymentAppFinder::InstallablePaymentApps& + installable_apps) override; void OnDoneCreatingPaymentApps() override; // PaymentResponseHelper::Delegate @@ -160,6 +178,9 @@ class PaymentRequestState : public PaymentAppFactory::Delegate, // "basic-card", but false for "https://bobpay.com". void AreRequestedMethodsSupported(MethodsSupportedCallback callback); + // Resets pending MethodsSupportedCallback after abort. + void OnAbort(); + // Returns authenticated user email, or empty string. std::string GetAuthenticatedEmail() const; @@ -330,8 +351,10 @@ class PaymentRequestState : public PaymentAppFactory::Delegate, SectionSelectionStatus selection_status); content::WebContents* web_contents_; + content::RenderFrameHost* initiator_render_frame_host_; const GURL top_origin_; const GURL frame_origin_; + const url::Origin frame_security_origin_; size_t number_of_payment_app_factories_ = 0; // True when the requested autofill data (shipping address and/or contact diff --git a/chromium/components/payments/content/payment_request_state_unittest.cc b/chromium/components/payments/content/payment_request_state_unittest.cc index f25dff519cf..7780428c267 100644 --- a/chromium/components/payments/content/payment_request_state_unittest.cc +++ b/chromium/components/payments/content/payment_request_state_unittest.cc @@ -80,10 +80,12 @@ class PaymentRequestStateTest : public testing::Test, PaymentAppServiceFactory::SetForTesting( std::make_unique<PaymentAppService>()); state_ = std::make_unique<PaymentRequestState>( - /*web_contents=*/nullptr, GURL("https://example.com"), - GURL("https://example.com/pay"), spec_.get(), this, "en-US", - &test_personal_data_manager_, &test_payment_request_delegate_, - base::Bind( + /*web_contents=*/nullptr, + /*render_frame_host=*/nullptr, GURL("https://example.com"), + 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_); diff --git a/chromium/components/payments/content/payment_response_helper_unittest.cc b/chromium/components/payments/content/payment_response_helper_unittest.cc index 9bbecf2b74e..96c67a2ffaf 100644 --- a/chromium/components/payments/content/payment_response_helper_unittest.cc +++ b/chromium/components/payments/content/payment_response_helper_unittest.cc @@ -33,12 +33,12 @@ class PaymentResponseHelperTest : public testing::Test, test_personal_data_manager_.AddProfile(address_); // Set up the autofill payment app. - autofill::CreditCard visa_card = autofill::test::GetCreditCard(); - visa_card.set_billing_address_id(address_.guid()); - visa_card.set_use_count(5u); + visa_card_ = autofill::test::GetCreditCard(); + visa_card_.set_billing_address_id(address_.guid()); + visa_card_.set_use_count(5u); autofill_app_ = std::make_unique<AutofillPaymentApp>( - "visa", visa_card, /*matches_merchant_card_type_exactly=*/true, - billing_addresses_, "en-US", &test_payment_request_delegate_); + "visa", visa_card_, billing_addresses_, "en-US", + &test_payment_request_delegate_); } ~PaymentResponseHelperTest() override {} @@ -90,6 +90,7 @@ class PaymentResponseHelperTest : public testing::Test, PaymentRequestSpec* spec() { return spec_.get(); } const mojom::PaymentResponsePtr& response() { return payment_response_; } autofill::AutofillProfile* test_address() { return &address_; } + const autofill::CreditCard& test_credit_card() { return visa_card_; } PaymentApp* test_app() { return autofill_app_.get(); } PaymentRequestDelegate* test_payment_request_delegate() { return &test_payment_request_delegate_; @@ -103,6 +104,7 @@ class PaymentResponseHelperTest : public testing::Test, // Test data. autofill::AutofillProfile address_; + autofill::CreditCard visa_card_; const std::vector<autofill::AutofillProfile*> billing_addresses_; std::unique_ptr<AutofillPaymentApp> autofill_app_; }; @@ -119,22 +121,27 @@ TEST_F(PaymentResponseHelperTest, GeneratePaymentResponse_SupportedMethod) { test_address(), this); EXPECT_EQ("visa", response()->method_name); EXPECT_EQ( - "{\"billingAddress\":" - "{\"addressLine\":[\"666 Erebus St.\",\"Apt 8\"]," - "\"city\":\"Elysium\"," - "\"country\":\"US\"," - "\"dependentLocality\":\"\"," - "\"organization\":\"Underworld\"," - "\"phone\":\"16502111111\"," - "\"postalCode\":\"91111\"," - "\"recipient\":\"John H. Doe\"," - "\"region\":\"CA\"," - "\"sortingCode\":\"\"}," - "\"cardNumber\":\"4111111111111111\"," - "\"cardSecurityCode\":\"123\"," - "\"cardholderName\":\"Test User\"," - "\"expiryMonth\":\"11\"," - "\"expiryYear\":\"2022\"}", + base::StringPrintf( + "{\"billingAddress\":" + "{\"addressLine\":[\"666 Erebus St.\",\"Apt 8\"]," + "\"city\":\"Elysium\"," + "\"country\":\"US\"," + "\"dependentLocality\":\"\"," + "\"organization\":\"Underworld\"," + "\"phone\":\"16502111111\"," + "\"postalCode\":\"91111\"," + "\"recipient\":\"John H. Doe\"," + "\"region\":\"CA\"," + "\"sortingCode\":\"\"}," + "\"cardNumber\":\"4111111111111111\"," + "\"cardSecurityCode\":\"123\"," + "\"cardholderName\":\"Test User\"," + "\"expiryMonth\":\"%s\"," + "\"expiryYear\":\"%s\"}", + base::UTF16ToUTF8(test_credit_card().Expiration2DigitMonthAsString()) + .c_str(), + base::UTF16ToUTF8(test_credit_card().Expiration4DigitYearAsString()) + .c_str()), response()->stringified_details); } @@ -157,22 +164,27 @@ TEST_F(PaymentResponseHelperTest, GeneratePaymentResponse_BasicCard) { test_address(), this); EXPECT_EQ("basic-card", response()->method_name); EXPECT_EQ( - "{\"billingAddress\":" - "{\"addressLine\":[\"666 Erebus St.\",\"Apt 8\"]," - "\"city\":\"Elysium\"," - "\"country\":\"US\"," - "\"dependentLocality\":\"\"," - "\"organization\":\"Underworld\"," - "\"phone\":\"16502111111\"," - "\"postalCode\":\"91111\"," - "\"recipient\":\"John H. Doe\"," - "\"region\":\"CA\"," - "\"sortingCode\":\"\"}," - "\"cardNumber\":\"4111111111111111\"," - "\"cardSecurityCode\":\"123\"," - "\"cardholderName\":\"Test User\"," - "\"expiryMonth\":\"11\"," - "\"expiryYear\":\"2022\"}", + base::StringPrintf( + "{\"billingAddress\":" + "{\"addressLine\":[\"666 Erebus St.\",\"Apt 8\"]," + "\"city\":\"Elysium\"," + "\"country\":\"US\"," + "\"dependentLocality\":\"\"," + "\"organization\":\"Underworld\"," + "\"phone\":\"16502111111\"," + "\"postalCode\":\"91111\"," + "\"recipient\":\"John H. Doe\"," + "\"region\":\"CA\"," + "\"sortingCode\":\"\"}," + "\"cardNumber\":\"4111111111111111\"," + "\"cardSecurityCode\":\"123\"," + "\"cardholderName\":\"Test User\"," + "\"expiryMonth\":\"%s\"," + "\"expiryYear\":\"%s\"}", + base::UTF16ToUTF8(test_credit_card().Expiration2DigitMonthAsString()) + .c_str(), + base::UTF16ToUTF8(test_credit_card().Expiration4DigitYearAsString()) + .c_str()), response()->stringified_details); } diff --git a/chromium/components/payments/content/service_worker_payment_app.cc b/chromium/components/payments/content/service_worker_payment_app.cc index 40cfdedba99..c6b87b07d70 100644 --- a/chromium/components/payments/content/service_worker_payment_app.cc +++ b/chromium/components/payments/content/service_worker_payment_app.cc @@ -10,10 +10,12 @@ #include "base/bind.h" #include "base/bind_helpers.h" +#include "base/feature_list.h" #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_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" @@ -180,6 +182,8 @@ ServiceWorkerPaymentApp::CreateCanMakePaymentEventData() { event_data->top_origin = top_origin_; event_data->payment_request_origin = frame_origin_; + if (base::FeatureList::IsEnabled(::features::kWebPaymentsMinimalUI)) + event_data->currency = spec_->details().total->amount->currency; DCHECK(spec_->details().modifiers); for (const auto& modifier : *spec_->details().modifiers) { @@ -210,10 +214,10 @@ void ServiceWorkerPaymentApp::OnCanMakePaymentEventSkipped( void ServiceWorkerPaymentApp::OnCanMakePaymentEventResponded( ValidateCanMakePaymentCallback callback, - bool result) { + mojom::CanMakePaymentResponsePtr response) { // |can_make_payment| is true as long as there is a matching payment handler. can_make_payment_result_ = true; - has_enrolled_instrument_result_ = result; + has_enrolled_instrument_result_ = response->can_make_payment; base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(std::move(callback), this, can_make_payment_result_)); @@ -389,16 +393,12 @@ bool ServiceWorkerPaymentApp::CanPreselect() const { return !GetLabel().empty() && !icon_image_.size().IsEmpty(); } -bool ServiceWorkerPaymentApp::IsExactlyMatchingMerchantRequest() const { - return true; -} - base::string16 ServiceWorkerPaymentApp::GetMissingInfoLabel() const { NOTREACHED(); return base::string16(); } -bool ServiceWorkerPaymentApp::IsValidForCanMakePayment() const { +bool ServiceWorkerPaymentApp::HasEnrolledInstrument() const { // This app should not be used when can_make_payment_result_ is false, so this // interface should not be invoked. DCHECK(can_make_payment_result_); @@ -409,6 +409,10 @@ void ServiceWorkerPaymentApp::RecordUse() { NOTIMPLEMENTED(); } +bool ServiceWorkerPaymentApp::NeedsInstallation() const { + return needs_installation_; +} + base::string16 ServiceWorkerPaymentApp::GetLabel() const { return base::UTF8ToUTF16(needs_installation_ ? installable_web_app_info_->name @@ -428,9 +432,7 @@ base::string16 ServiceWorkerPaymentApp::GetSublabel() const { bool ServiceWorkerPaymentApp::IsValidForModifier( const std::string& method, bool supported_networks_specified, - const std::set<std::string>& supported_networks, - bool supported_types_specified, - const std::set<autofill::CreditCard::CardType>& supported_types) const { + const std::set<std::string>& supported_networks) const { // Payment app that needs installation only supports url based payment // methods. if (needs_installation_) @@ -447,9 +449,8 @@ bool ServiceWorkerPaymentApp::IsValidForModifier( return true; // Checking the capabilities of this app against the modifier. - // Return true if both card networks and types are not specified in the - // modifier. - if (!supported_networks_specified && !supported_types_specified) + // Return true if card networks are not specified in the modifier. + if (!supported_networks_specified) return true; // Return false if no capabilities for this app. @@ -473,21 +474,6 @@ bool ServiceWorkerPaymentApp::IsValidForModifier( } } - if (supported_types_specified) { - std::set<autofill::CreditCard::CardType> app_supported_types; - for (const auto& type : - stored_payment_app_info_->capabilities[i].supported_card_types) { - app_supported_types.insert( - GetBasicCardType(static_cast<mojom::BasicCardType>(type))); - } - - if (base::STLSetIntersection<std::set<autofill::CreditCard::CardType>>( - app_supported_types, supported_types) - .empty()) { - continue; - } - } - break; } @@ -545,4 +531,19 @@ void ServiceWorkerPaymentApp::OnPaymentAppIdentity(const url::Origin& origin, identity_callback_.Run(origin, registration_id); } +ukm::SourceId ServiceWorkerPaymentApp::UkmSourceId() { + if (ukm_source_id_ == ukm::kInvalidSourceId) { + GURL sw_scope = needs_installation_ + ? GURL(installable_web_app_info_->sw_scope) + : stored_payment_app_info_->scope; + // At this point we know that the payment handler window is open for this + // app since this getter is called for the invoked app inside the + // PaymentRequest::OnPaymentHandlerOpenWindowCalled function. + ukm_source_id_ = + content::PaymentAppProvider::GetInstance() + ->GetSourceIdForPaymentAppFromScope(sw_scope.GetOrigin()); + } + return ukm_source_id_; +} + } // 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 03e09ad547f..f6e5130038f 100644 --- a/chromium/components/payments/content/service_worker_payment_app.h +++ b/chromium/components/payments/content/service_worker_payment_app.h @@ -81,24 +81,23 @@ class ServiceWorkerPaymentApp : public PaymentApp { bool IsCompleteForPayment() const override; uint32_t GetCompletenessScore() const override; bool CanPreselect() const override; - bool IsExactlyMatchingMerchantRequest() const override; base::string16 GetMissingInfoLabel() const override; - bool IsValidForCanMakePayment() const override; + bool HasEnrolledInstrument() const override; void RecordUse() override; + bool NeedsInstallation() 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, - bool supported_types_specified, - const std::set<autofill::CreditCard::CardType>& - supported_types) 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; gfx::ImageSkia icon_image_skia() const 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) { @@ -113,8 +112,9 @@ class ServiceWorkerPaymentApp : public PaymentApp { mojom::CanMakePaymentEventDataPtr CreateCanMakePaymentEventData(); void OnCanMakePaymentEventSkipped(ValidateCanMakePaymentCallback callback); - void OnCanMakePaymentEventResponded(ValidateCanMakePaymentCallback callback, - bool result); + void OnCanMakePaymentEventResponded( + ValidateCanMakePaymentCallback callback, + mojom::CanMakePaymentResponsePtr response); // Called from two places: // 1) From PaymentAppProvider after a just-in-time installable payment handler @@ -154,6 +154,8 @@ class ServiceWorkerPaymentApp : public PaymentApp { std::unique_ptr<WebAppInstallationInfo> installable_web_app_info_; std::string installable_enabled_method_; + ukm::SourceId ukm_source_id_ = ukm::kInvalidSourceId; + base::WeakPtrFactory<ServiceWorkerPaymentApp> weak_ptr_factory_{this}; DISALLOW_COPY_AND_ASSIGN(ServiceWorkerPaymentApp); 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 fe0caf312d8..047613adb64 100644 --- a/chromium/components/payments/content/service_worker_payment_app_factory.cc +++ b/chromium/components/payments/content/service_worker_payment_app_factory.cc @@ -17,6 +17,18 @@ #include "content/public/browser/web_contents.h" namespace payments { +namespace { + +std::vector<mojom::PaymentMethodDataPtr> Clone( + const std::vector<mojom::PaymentMethodDataPtr>& original) { + std::vector<mojom::PaymentMethodDataPtr> clone(original.size()); + std::transform( + original.begin(), original.end(), clone.begin(), + [](const mojom::PaymentMethodDataPtr& item) { return item.Clone(); }); + return clone; +} + +} // namespace class ServiceWorkerPaymentAppCreator { public: @@ -45,14 +57,21 @@ class ServiceWorkerPaymentAppCreator { return; } + if (delegate_->SkipCreatingNativePaymentApps()) { + delegate_->OnCreatingNativePaymentAppsSkipped( + std::move(apps), std::move(installable_apps)); + FinishAndCleanup(); + return; + } + 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::Bind(&PaymentAppFactory::Delegate::OnPaymentAppInstalled, - delegate_)); + base::BindRepeating( + &PaymentAppFactory::Delegate::OnPaymentAppInstalled, delegate_)); app->ValidateCanMakePayment(base::BindOnce( &ServiceWorkerPaymentAppCreator::OnSWPaymentAppValidated, weak_ptr_factory_.GetWeakPtr())); @@ -66,8 +85,8 @@ class ServiceWorkerPaymentAppCreator { delegate_->GetFrameOrigin(), delegate_->GetSpec(), std::move(installable_app.second), installable_app.first.spec(), delegate_->GetPaymentRequestDelegate(), - base::Bind(&PaymentAppFactory::Delegate::OnPaymentAppInstalled, - delegate_)); + base::BindRepeating( + &PaymentAppFactory::Delegate::OnPaymentAppInstalled, delegate_)); app->ValidateCanMakePayment(base::BindOnce( &ServiceWorkerPaymentAppCreator::OnSWPaymentAppValidated, weak_ptr_factory_.GetWeakPtr())); @@ -125,13 +144,15 @@ void ServiceWorkerPaymentAppFactory::Create(base::WeakPtr<Delegate> delegate) { /*owner=*/this, delegate); ServiceWorkerPaymentAppCreator* creator_raw_pointer = creator.get(); creators_[creator_raw_pointer] = std::move(creator); + ServiceWorkerPaymentAppFinder::GetInstance()->GetAllPaymentApps( - delegate->GetWebContents(), - delegate->GetPaymentRequestDelegate()->GetPaymentManifestWebDataService(), - delegate->GetSpec()->method_data(), + delegate->GetFrameSecurityOrigin(), + delegate->GetInitiatorRenderFrameHost(), delegate->GetWebContents(), + delegate->GetPaymentManifestWebDataService(), + Clone(delegate->GetMethodData()), delegate->MayCrawlForInstallablePaymentApps(), - base::Bind(&ServiceWorkerPaymentAppCreator::CreatePaymentApps, - creator_raw_pointer->GetWeakPtr()), + 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. 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 d2a0e89e315..72c000a627c 100644 --- a/chromium/components/payments/content/service_worker_payment_app_finder.cc +++ b/chromium/components/payments/content/service_worker_payment_app_finder.cc @@ -24,6 +24,7 @@ #include "components/payments/core/payment_manifest_downloader.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_thread.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" @@ -54,14 +55,11 @@ bool BasicCardCapabilitiesMatch( const mojom::PaymentMethodDataPtr& request) { for (const auto& capability : capabilities) { if (CapabilityMatches(request->supported_networks, - capability.supported_card_networks) && - CapabilityMatches(request->supported_types, - capability.supported_card_types)) { + capability.supported_card_networks)) { return true; } } - return capabilities.empty() && request->supported_networks.empty() && - request->supported_types.empty(); + return capabilities.empty() && request->supported_networks.empty(); } // Returns true if |app| supports at least one of the |requests|. @@ -101,6 +99,8 @@ class SelfDeletingServiceWorkerPaymentAppFinder { // this factory. Don't destroy the factory and don't call this method again // until |finished_using_resources_callback| has run. 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, @@ -115,13 +115,15 @@ class SelfDeletingServiceWorkerPaymentAppFinder { std::make_unique<DeveloperConsoleLogger>(web_contents)); cache_ = cache; verifier_ = std::make_unique<ManifestVerifier>( - web_contents, downloader_.get(), parser_.get(), cache_.get()); + merchant_origin, web_contents, downloader_.get(), parser_.get(), + cache_.get()); if (may_crawl_for_installable_payment_apps && base::FeatureList::IsEnabled( features::kWebPaymentsJustInTimePaymentApp)) { // Construct crawler in constructor to allow it observe the web_contents. crawler_ = std::make_unique<InstallablePaymentAppCrawler>( - web_contents, downloader_.get(), parser_.get(), cache_.get()); + merchant_origin, initiator_render_frame_host, web_contents, + downloader_.get(), parser_.get(), cache_.get()); if (ignore_port_in_origin_comparison_for_testing_) crawler_->IgnorePortInOriginComparisonForTesting(); } @@ -147,10 +149,27 @@ class SelfDeletingServiceWorkerPaymentAppFinder { } private: + static void RemoveUnrequestedMethods( + const std::vector<mojom::PaymentMethodDataPtr>& requested_method_data, + content::PaymentAppProvider::PaymentApps* apps) { + std::set<std::string> requested_methods; + for (const auto& requested_method_datum : requested_method_data) { + requested_methods.insert(requested_method_datum->supported_method); + } + for (auto& app : *apps) { + std::sort(app.second->enabled_methods.begin(), + app.second->enabled_methods.end()); + app.second->enabled_methods = + base::STLSetIntersection<std::vector<std::string>>( + app.second->enabled_methods, requested_methods); + } + } + void OnGotAllPaymentApps(content::PaymentAppProvider::PaymentApps apps) { if (ignore_port_in_origin_comparison_for_testing_) RemovePortNumbersFromScopesForTest(&apps); + RemoveUnrequestedMethods(requested_method_data_, &apps); ServiceWorkerPaymentAppFinder::RemoveAppsWithoutMatchingMethodData( requested_method_data_, &apps); if (apps.empty()) { @@ -266,15 +285,31 @@ ServiceWorkerPaymentAppFinder* ServiceWorkerPaymentAppFinder::GetInstance() { } void ServiceWorkerPaymentAppFinder::GetAllPaymentApps( + const url::Origin& merchant_origin, + content::RenderFrameHost* initiator_render_frame_host, content::WebContents* web_contents, scoped_refptr<PaymentManifestWebDataService> cache, - const std::vector<mojom::PaymentMethodDataPtr>& requested_method_data, + std::vector<mojom::PaymentMethodDataPtr> requested_method_data, bool may_crawl_for_installable_payment_apps, GetAllPaymentAppsCallback callback, base::OnceClosure finished_writing_cache_callback_for_testing) { + DCHECK(!requested_method_data.empty()); + // Do not look up payment handlers for ignored payment methods. + base::EraseIf(requested_method_data, + [&](const mojom::PaymentMethodDataPtr& method_data) { + return base::Contains(ignored_methods_, + method_data->supported_method); + }); + if (requested_method_data.empty()) { + std::move(callback).Run( + content::PaymentAppProvider::PaymentApps(), + std::map<GURL, std::unique_ptr<WebAppInstallationInfo>>(), + /*error_message=*/""); + return; + } + SelfDeletingServiceWorkerPaymentAppFinder* self_delete_factory = new SelfDeletingServiceWorkerPaymentAppFinder(); - std::unique_ptr<PaymentManifestDownloader> downloader; if (test_downloader_ != nullptr) { downloader = std::move(test_downloader_); @@ -288,7 +323,8 @@ void ServiceWorkerPaymentAppFinder::GetAllPaymentApps( } self_delete_factory->GetAllPaymentApps( - web_contents, std::move(downloader), cache, requested_method_data, + merchant_origin, initiator_render_frame_host, web_contents, + std::move(downloader), cache, requested_method_data, may_crawl_for_installable_payment_apps, std::move(callback), std::move(finished_writing_cache_callback_for_testing)); } @@ -307,10 +343,16 @@ void ServiceWorkerPaymentAppFinder::RemoveAppsWithoutMatchingMethodData( } } +void ServiceWorkerPaymentAppFinder::IgnorePaymentMethodForTest( + const std::string& method) { + ignored_methods_.insert(method); +} + ServiceWorkerPaymentAppFinder::ServiceWorkerPaymentAppFinder() - : test_downloader_(nullptr) {} + : ignored_methods_({methods::kGooglePlayBilling}), + test_downloader_(nullptr) {} -ServiceWorkerPaymentAppFinder::~ServiceWorkerPaymentAppFinder() {} +ServiceWorkerPaymentAppFinder::~ServiceWorkerPaymentAppFinder() = default; void ServiceWorkerPaymentAppFinder:: SetDownloaderAndIgnorePortInOriginComparisonForTesting( 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 4a726a32829..04d7fa64147 100644 --- a/chromium/components/payments/content/service_worker_payment_app_finder.h +++ b/chromium/components/payments/content/service_worker_payment_app_finder.h @@ -27,9 +27,14 @@ struct DefaultSingletonTraits; } // namespace base namespace content { +class RenderFrameHost; class WebContents; } // namespace content +namespace url { +class Origin; +} // namespace url + namespace payments { class PaymentManifestDownloader; @@ -51,6 +56,10 @@ class ServiceWorkerPaymentAppFinder { // |requested_method_data|, verifies these apps are allowed to handle these // payment methods, and filters them by their capabilities. // + // |merchant_origin| should be 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'. + // // The payment apps will be returned through |callback|. After |callback| has // been invoked, it's safe to show the apps in UI for user to select one of // these apps for payment. @@ -61,9 +70,11 @@ 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, - const std::vector<mojom::PaymentMethodDataPtr>& requested_method_data, + std::vector<mojom::PaymentMethodDataPtr> requested_method_data, bool may_crawl_for_installable_payment_apps, GetAllPaymentAppsCallback callback, base::OnceClosure finished_writing_cache_callback_for_testing); @@ -74,13 +85,16 @@ class ServiceWorkerPaymentAppFinder { const std::vector<mojom::PaymentMethodDataPtr>& requested_method_data, content::PaymentAppProvider::PaymentApps* apps); + // Ignore the given |method|, so that no installed or installable service + // workers would ever be looked up in GetAllPaymentApps(). Calling this + // multiple times will union the new payment methods with the existing set. + void IgnorePaymentMethodForTest(const std::string& method); + private: friend struct base::DefaultSingletonTraits<ServiceWorkerPaymentAppFinder>; friend class PaymentRequestPaymentAppTest; friend class ServiceWorkerPaymentAppFinderBrowserTest; - friend class HybridRequestSkipUITest; - friend class JourneyLoggerTest; - friend class PaymentHandlerJustInTimeInstallationTest; + friend class PaymentRequestPlatformBrowserTestBase; ServiceWorkerPaymentAppFinder(); ~ServiceWorkerPaymentAppFinder(); @@ -91,6 +105,7 @@ class ServiceWorkerPaymentAppFinder { void SetDownloaderAndIgnorePortInOriginComparisonForTesting( std::unique_ptr<PaymentManifestDownloader> downloader); + std::set<std::string> ignored_methods_; std::unique_ptr<PaymentManifestDownloader> test_downloader_; DISALLOW_COPY_AND_ASSIGN(ServiceWorkerPaymentAppFinder); diff --git a/chromium/components/payments/content/service_worker_payment_app_finder_unittest.cc b/chromium/components/payments/content/service_worker_payment_app_finder_unittest.cc index b673962574e..32ab27500ca 100644 --- a/chromium/components/payments/content/service_worker_payment_app_finder_unittest.cc +++ b/chromium/components/payments/content/service_worker_payment_app_finder_unittest.cc @@ -105,21 +105,6 @@ TEST_F(ServiceWorkerPaymentAppFinderTest, } TEST_F(ServiceWorkerPaymentAppFinderTest, - RemoveAppsWithoutMatchingMethodData_NoTypeCapabilities) { - std::vector<mojom::PaymentMethodDataPtr> requested_methods; - requested_methods.emplace_back(mojom::PaymentMethodData::New()); - requested_methods.back()->supported_method = "basic-card"; - requested_methods.back()->supported_types = {mojom::BasicCardType::DEBIT}; - content::PaymentAppProvider::PaymentApps apps; - apps[0] = std::make_unique<content::StoredPaymentApp>(); - apps[0]->enabled_methods = {"basic-card"}; - - RemoveAppsWithoutMatchingMethodData(requested_methods, &apps); - - EXPECT_TRUE(apps.empty()); -} - -TEST_F(ServiceWorkerPaymentAppFinderTest, RemoveAppsWithoutMatchingMethodData_NoMatchingNetworkCapabilities) { std::vector<mojom::PaymentMethodDataPtr> requested_methods; requested_methods.emplace_back(mojom::PaymentMethodData::New()); @@ -139,25 +124,7 @@ TEST_F(ServiceWorkerPaymentAppFinderTest, } TEST_F(ServiceWorkerPaymentAppFinderTest, - RemoveAppsWithoutMatchingMethodData_NoMatchingTypeCapabilities) { - std::vector<mojom::PaymentMethodDataPtr> requested_methods; - requested_methods.emplace_back(mojom::PaymentMethodData::New()); - requested_methods.back()->supported_method = "basic-card"; - requested_methods.back()->supported_types = {mojom::BasicCardType::DEBIT}; - content::PaymentAppProvider::PaymentApps apps; - apps[0] = std::make_unique<content::StoredPaymentApp>(); - apps[0]->enabled_methods = {"basic-card"}; - apps[0]->capabilities.emplace_back(); - apps[0]->capabilities.back().supported_card_types = { - static_cast<int32_t>(mojom::BasicCardType::CREDIT)}; - - RemoveAppsWithoutMatchingMethodData(requested_methods, &apps); - - EXPECT_TRUE(apps.empty()); -} - -TEST_F(ServiceWorkerPaymentAppFinderTest, - RemoveAppsWithoutMatchingMethodData_NoRequestedNetworkOrType) { + RemoveAppsWithoutMatchingMethodData_NoRequestedNetwork) { std::vector<mojom::PaymentMethodDataPtr> requested_methods; requested_methods.emplace_back(mojom::PaymentMethodData::New()); requested_methods.back()->supported_method = "basic-card"; @@ -167,8 +134,6 @@ TEST_F(ServiceWorkerPaymentAppFinderTest, apps[0]->capabilities.emplace_back(); apps[0]->capabilities.back().supported_card_networks = { static_cast<int32_t>(mojom::BasicCardNetwork::VISA)}; - apps[0]->capabilities.back().supported_card_types = { - static_cast<int32_t>(mojom::BasicCardType::CREDIT)}; RemoveAppsWithoutMatchingMethodData(requested_methods, &apps); @@ -178,21 +143,16 @@ TEST_F(ServiceWorkerPaymentAppFinderTest, EXPECT_EQ(std::vector<std::string>{"basic-card"}, actual->enabled_methods); ASSERT_EQ(1U, actual->capabilities.size()); const auto& capability = actual->capabilities.back(); - ASSERT_EQ(1U, capability.supported_card_types.size()); - EXPECT_EQ(static_cast<int32_t>(mojom::BasicCardType::CREDIT), - capability.supported_card_types[0]); ASSERT_EQ(1U, actual->capabilities.back().supported_card_networks.size()); EXPECT_EQ(static_cast<int32_t>(mojom::BasicCardNetwork::VISA), capability.supported_card_networks[0]); } TEST_F(ServiceWorkerPaymentAppFinderTest, - RemoveAppsWithoutMatchingMethodData_IntersectionOfNetworksAndTypes) { + RemoveAppsWithoutMatchingMethodData_IntersectionOfNetworks) { std::vector<mojom::PaymentMethodDataPtr> requested_methods; requested_methods.emplace_back(mojom::PaymentMethodData::New()); requested_methods.back()->supported_method = "basic-card"; - requested_methods.back()->supported_types = {mojom::BasicCardType::DEBIT, - mojom::BasicCardType::CREDIT}; requested_methods.back()->supported_networks = { mojom::BasicCardNetwork::AMEX, mojom::BasicCardNetwork::DINERS}; content::PaymentAppProvider::PaymentApps apps; @@ -202,9 +162,6 @@ TEST_F(ServiceWorkerPaymentAppFinderTest, apps[0]->capabilities.back().supported_card_networks = { static_cast<int32_t>(mojom::BasicCardNetwork::DINERS), static_cast<int32_t>(mojom::BasicCardNetwork::VISA)}; - apps[0]->capabilities.back().supported_card_types = { - static_cast<int32_t>(mojom::BasicCardType::PREPAID), - static_cast<int32_t>(mojom::BasicCardType::DEBIT)}; RemoveAppsWithoutMatchingMethodData(requested_methods, &apps); @@ -214,10 +171,6 @@ TEST_F(ServiceWorkerPaymentAppFinderTest, EXPECT_EQ(std::vector<std::string>{"basic-card"}, actual->enabled_methods); ASSERT_EQ(1U, actual->capabilities.size()); const auto& capability = actual->capabilities.back(); - EXPECT_EQ( - (std::vector<int32_t>{static_cast<int32_t>(mojom::BasicCardType::PREPAID), - static_cast<int32_t>(mojom::BasicCardType::DEBIT)}), - capability.supported_card_types); EXPECT_EQ((std::vector<int32_t>{ static_cast<int32_t>(mojom::BasicCardNetwork::DINERS), static_cast<int32_t>(mojom::BasicCardNetwork::VISA)}), @@ -229,13 +182,10 @@ TEST_F(ServiceWorkerPaymentAppFinderTest, std::vector<mojom::PaymentMethodDataPtr> requested_methods; requested_methods.emplace_back(mojom::PaymentMethodData::New()); requested_methods.back()->supported_method = "unknown-method"; - requested_methods.back()->supported_types = {mojom::BasicCardType::DEBIT}; content::PaymentAppProvider::PaymentApps apps; apps[0] = std::make_unique<content::StoredPaymentApp>(); apps[0]->enabled_methods = {"unknown-method"}; apps[0]->capabilities.emplace_back(); - apps[0]->capabilities.back().supported_card_types = { - static_cast<int32_t>(mojom::BasicCardType::PREPAID)}; RemoveAppsWithoutMatchingMethodData(requested_methods, &apps); 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 2347f1e06de..3128121c1ab 100644 --- a/chromium/components/payments/content/service_worker_payment_app_unittest.cc +++ b/chromium/components/payments/content/service_worker_payment_app_unittest.cc @@ -76,7 +76,6 @@ class ServiceWorkerPaymentAppTest : public testing::Test, entry_1->supported_networks.push_back(mojom::BasicCardNetwork::UNIONPAY); entry_1->supported_networks.push_back(mojom::BasicCardNetwork::JCB); entry_1->supported_networks.push_back(mojom::BasicCardNetwork::VISA); - entry_1->supported_types.push_back(mojom::BasicCardType::DEBIT); method_data.push_back(std::move(entry_1)); mojom::PaymentMethodDataPtr entry_2 = mojom::PaymentMethodData::New(); @@ -109,8 +108,6 @@ class ServiceWorkerPaymentAppTest : public testing::Test, static_cast<int32_t>(mojom::BasicCardNetwork::UNIONPAY)); stored_app->capabilities.back().supported_card_networks.emplace_back( static_cast<int32_t>(mojom::BasicCardNetwork::JCB)); - stored_app->capabilities.back().supported_card_types.emplace_back( - static_cast<int32_t>(mojom::BasicCardType::DEBIT)); stored_app->user_hint = "Visa 4012 ... 1881"; stored_app->prefer_related_applications = false; @@ -119,7 +116,7 @@ class ServiceWorkerPaymentAppTest : public testing::Test, &browser_context_, GURL("https://testmerchant.com"), GURL("https://testmerchant.com/bobpay"), spec_.get(), std::move(stored_app), &delegate_, - base::Bind( + base::BindRepeating( [](const url::Origin& origin, int64_t registration_id) { /* Intentionally left blank. */ })); } @@ -151,7 +148,6 @@ TEST_F(ServiceWorkerPaymentAppTest, AppInfo) { CreateServiceWorkerPaymentApp(true); EXPECT_TRUE(GetApp()->IsCompleteForPayment()); - EXPECT_TRUE(GetApp()->IsExactlyMatchingMerchantRequest()); EXPECT_EQ(base::UTF16ToUTF8(GetApp()->GetLabel()), "bobpay"); EXPECT_EQ(base::UTF16ToUTF8(GetApp()->GetSublabel()), "bobpay.com"); @@ -176,7 +172,6 @@ TEST_F(ServiceWorkerPaymentAppTest, CreatePaymentRequestEventData) { EXPECT_EQ(event_data->method_data.size(), 2U); EXPECT_EQ(event_data->method_data[0]->supported_method, "basic-card"); EXPECT_EQ(event_data->method_data[0]->supported_networks.size(), 3U); - EXPECT_EQ(event_data->method_data[0]->supported_types.size(), 1U); EXPECT_EQ(event_data->method_data[1]->supported_method, "https://bobpay.com"); EXPECT_EQ(event_data->total->currency, "USD"); @@ -228,32 +223,28 @@ TEST_F(ServiceWorkerPaymentAppTest, ValidateCanMakePayment) { CreateServiceWorkerPaymentApp(/*with_url_method=*/true); GetApp()->ValidateCanMakePayment(base::BindOnce( [](ServiceWorkerPaymentApp*, bool result) { EXPECT_TRUE(result); })); - EXPECT_FALSE(GetApp()->IsValidForCanMakePayment()); + EXPECT_FALSE(GetApp()->HasEnrolledInstrument()); } // Test modifiers can be matched based on capabilities. TEST_F(ServiceWorkerPaymentAppTest, IsValidForModifier) { CreateServiceWorkerPaymentApp(true); - EXPECT_TRUE(GetApp()->IsValidForModifier("basic-card", false, {}, false, {})); - - EXPECT_TRUE( - GetApp()->IsValidForModifier("https://bobpay.com", true, {}, true, {})); - - EXPECT_FALSE(GetApp()->IsValidForModifier("basic-card", true, {"mastercard"}, - false, {})); - - EXPECT_TRUE(GetApp()->IsValidForModifier("basic-card", true, {"unionpay"}, - false, {})); + EXPECT_TRUE(GetApp()->IsValidForModifier( + /*method=*/"basic-card", /*supported_networks_specified=*/false, + /*supported_networks=*/{})); EXPECT_TRUE(GetApp()->IsValidForModifier( - "basic-card", true, {"unionpay"}, true, - {autofill::CreditCard::CardType::CARD_TYPE_DEBIT, - autofill::CreditCard::CardType::CARD_TYPE_CREDIT})); + /*method=*/"https://bobpay.com", /*supported_networks_specified=*/true, + /*supported_networks=*/{})); EXPECT_FALSE(GetApp()->IsValidForModifier( - "basic-card", true, {"unionpay"}, true, - {autofill::CreditCard::CardType::CARD_TYPE_CREDIT})); + /*method=*/"basic-card", /*supported_networks_specified=*/true, + /*supported_networks=*/{"mastercard"})); + + EXPECT_TRUE(GetApp()->IsValidForModifier( + /*method=*/"basic-card", /*supported_networks_specified=*/true, + /*supported_networks=*/{"unionpay"})); } } // namespace payments diff --git a/chromium/components/payments/content/utility/BUILD.gn b/chromium/components/payments/content/utility/BUILD.gn index f51b14dce88..08e459b07bc 100644 --- a/chromium/components/payments/content/utility/BUILD.gn +++ b/chromium/components/payments/content/utility/BUILD.gn @@ -39,9 +39,7 @@ source_set("unit_tests") { } fuzzer_test("payment_method_manifest_fuzzer") { - sources = [ - "payment_method_manifest_parser_fuzzer.cc", - ] + sources = [ "payment_method_manifest_parser_fuzzer.cc" ] deps = [ ":utility", "//base", @@ -55,9 +53,7 @@ fuzzer_test("payment_method_manifest_fuzzer") { } fuzzer_test("payment_web_app_manifest_fuzzer") { - sources = [ - "payment_web_app_manifest_parser_fuzzer.cc", - ] + sources = [ "payment_web_app_manifest_parser_fuzzer.cc" ] deps = [ ":utility", "//base", @@ -70,9 +66,7 @@ fuzzer_test("payment_web_app_manifest_fuzzer") { } fuzzer_test("fingerprint_fuzzer") { - sources = [ - "fingerprint_parser_fuzzer.cc", - ] + sources = [ "fingerprint_parser_fuzzer.cc" ] deps = [ ":utility", "//base", diff --git a/chromium/components/payments/content/utility/payment_manifest_parser.cc b/chromium/components/payments/content/utility/payment_manifest_parser.cc index bcc3fcb31ff..88719c50034 100644 --- a/chromium/components/payments/content/utility/payment_manifest_parser.cc +++ b/chromium/components/payments/content/utility/payment_manifest_parser.cc @@ -68,8 +68,10 @@ const std::string ValidateAndTruncateIfNeeded(const std::string& input, } // Parses the "default_applications": ["https://some/url"] from |dict| into -// |web_app_manifest_urls|. Returns 'false' for invalid data. -bool ParseDefaultApplications(base::DictionaryValue* dict, +// |web_app_manifest_urls|. Uses |manifest_url| to resolve relative URLs. +// Returns 'false' for invalid data. +bool ParseDefaultApplications(const GURL& manifest_url, + base::DictionaryValue* dict, std::vector<GURL>* web_app_manifest_urls, const ErrorLogger& log) { DCHECK(dict); @@ -77,6 +79,8 @@ bool ParseDefaultApplications(base::DictionaryValue* dict, base::ListValue* list = nullptr; if (!dict->GetList(kDefaultApplications, &list)) { + // TODO(crbug.com/1065337): Move the error message strings to + // components/payments/core/native_error_strings.cc. log.Error( base::StringPrintf("\"%s\" must be a list.", kDefaultApplications)); return false; @@ -92,18 +96,17 @@ bool ParseDefaultApplications(base::DictionaryValue* dict, for (size_t i = 0; i < apps_number; ++i) { std::string item; if (!list->GetString(i, &item) || item.empty() || - !base::IsStringUTF8(item) || - !(base::StartsWith(item, kHttpsPrefix, base::CompareCase::SENSITIVE) || - base::StartsWith(item, kHttpPrefix, base::CompareCase::SENSITIVE))) { - log.Error(base::StringPrintf( - "Each entry in \"%s\" must be UTF8 string that starts with \"%s\" or " - "\"%s\" (for localhost).", - kDefaultApplications, kHttpsPrefix, kHttpPrefix)); + !base::IsStringUTF8(item)) { + log.Error(base::StringPrintf("Each entry in \"%s\" must be UTF8 string.", + kDefaultApplications)); web_app_manifest_urls->clear(); return false; } - GURL url(item); + GURL url = manifest_url.Resolve(item); + // TODO(crbug.com/1065337): Check that |url| is the same origin with + // |manifest_url|. Currently that's checked by callers, but the earlier this + // is caught, the fewer resources Chrome consumes. if (!UrlUtil::IsValidManifestUrl(url)) { const std::string item_to_print = ValidateAndTruncateIfNeeded(item, nullptr); @@ -353,6 +356,7 @@ PaymentManifestParser::PaymentManifestParser(std::unique_ptr<ErrorLogger> log) PaymentManifestParser::~PaymentManifestParser() = default; void PaymentManifestParser::ParsePaymentMethodManifest( + const GURL& manifest_url, const std::string& content, PaymentMethodCallback callback) { parse_payment_callback_counter_++; @@ -360,7 +364,8 @@ void PaymentManifestParser::ParsePaymentMethodManifest( data_decoder::DataDecoder::ParseJsonIsolated( content, base::BindOnce(&PaymentManifestParser::OnPaymentMethodParse, - weak_factory_.GetWeakPtr(), std::move(callback))); + weak_factory_.GetWeakPtr(), manifest_url, + std::move(callback))); } void PaymentManifestParser::ParseWebAppManifest(const std::string& content, @@ -384,6 +389,7 @@ void PaymentManifestParser::ParseWebAppInstallationInfo( // static void PaymentManifestParser::ParsePaymentMethodManifestIntoVectors( + const GURL& manifest_url, std::unique_ptr<base::Value> value, const ErrorLogger& log, std::vector<GURL>* web_app_manifest_urls, @@ -403,7 +409,8 @@ void PaymentManifestParser::ParsePaymentMethodManifestIntoVectors( } if (dict->HasKey(kDefaultApplications) && - !ParseDefaultApplications(dict.get(), web_app_manifest_urls, log)) { + !ParseDefaultApplications(manifest_url, dict.get(), web_app_manifest_urls, + log)) { return; } @@ -555,8 +562,9 @@ bool PaymentManifestParser::ParseWebAppInstallationInfoIntoStructs( { base::DictionaryValue* service_worker_dict = nullptr; if (!dict->GetDictionary(kServiceWorker, &service_worker_dict)) { - log.Error( - base::StringPrintf("\"%s\" must be a dictionary", kServiceWorker)); + log.Error(base::StringPrintf( + "\"%s\" must be a dictionary in your web app manifest.", + kServiceWorker)); return false; } @@ -640,6 +648,7 @@ bool PaymentManifestParser::ParseWebAppInstallationInfoIntoStructs( } void PaymentManifestParser::OnPaymentMethodParse( + const GURL& manifest_url, PaymentMethodCallback callback, data_decoder::DataDecoder::ValueOrError result) { parse_payment_callback_counter_--; @@ -650,8 +659,9 @@ void PaymentManifestParser::OnPaymentMethodParse( if (result.value) { ParsePaymentMethodManifestIntoVectors( - base::Value::ToUniquePtrValue(std::move(*result.value)), *log_, - &web_app_manifest_urls, &supported_origins, &all_origins_supported); + manifest_url, base::Value::ToUniquePtrValue(std::move(*result.value)), + *log_, &web_app_manifest_urls, &supported_origins, + &all_origins_supported); } else { log_->Error(*result.error); } diff --git a/chromium/components/payments/content/utility/payment_manifest_parser.h b/chromium/components/payments/content/utility/payment_manifest_parser.h index acf79969d63..a304c94a20a 100644 --- a/chromium/components/payments/content/utility/payment_manifest_parser.h +++ b/chromium/components/payments/content/utility/payment_manifest_parser.h @@ -29,14 +29,14 @@ class ErrorLogger; // Example 1 of valid payment method manifest structure: // // { -// "default_applications": ["https://bobpay.com/payment-app.json"], +// "default_applications": ["payment-app.json"], // "supported_origins": ["https://alicepay.com"] // } // // Example 2 of valid payment method manifest structure: // // { -// "default_applications": ["https://bobpay.com/payment-app.json"], +// "default_applications": ["payment-app.json"], // "supported_origins": "*" // } // @@ -69,7 +69,9 @@ class ErrorLogger; // } // // Specs: -// https://docs.google.com/document/d/1izV4uC-tiRJG3JLooqY3YRLU22tYOsLTNq0P_InPJeE +// https://developers.google.com/web/fundamentals/payments/payment-apps-developer-guide/web-payment-apps +// https://developers.google.com/web/fundamentals/payments/payment-apps-developer-guide/android-payment-apps +// https://w3c.github.io/payment-method-manifest/ // https://w3c.github.io/manifest/ // // Note the JSON parsing is done using the DataDecoder (either OOP or in a safe @@ -91,6 +93,8 @@ class PaymentManifestParser { std::string type; }; + // TODO(crbug.com/1065337): Return manifest parser errors to caller. + // Called on successful parsing of a payment method manifest. Parse failure // results in empty vectors and "false". using PaymentMethodCallback = base::OnceCallback< @@ -109,7 +113,8 @@ class PaymentManifestParser { explicit PaymentManifestParser(std::unique_ptr<ErrorLogger> log); ~PaymentManifestParser(); - void ParsePaymentMethodManifest(const std::string& content, + void ParsePaymentMethodManifest(const GURL& manifest_url, + const std::string& content, PaymentMethodCallback callback); void ParseWebAppManifest(const std::string& content, WebAppCallback callback); @@ -122,6 +127,7 @@ class PaymentManifestParser { // Visible for tests. static void ParsePaymentMethodManifestIntoVectors( + const GURL& manifest_url, std::unique_ptr<base::Value> value, const ErrorLogger& log, std::vector<GURL>* web_app_manifest_urls, @@ -140,7 +146,8 @@ class PaymentManifestParser { std::vector<WebAppIcon>* icons); private: - void OnPaymentMethodParse(PaymentMethodCallback callback, + void OnPaymentMethodParse(const GURL& manifest_url, + PaymentMethodCallback callback, data_decoder::DataDecoder::ValueOrError result); void OnWebAppParse(WebAppCallback callback, data_decoder::DataDecoder::ValueOrError result); diff --git a/chromium/components/payments/content/utility/payment_manifest_parser_unittest.cc b/chromium/components/payments/content/utility/payment_manifest_parser_unittest.cc index 8b08fe37372..fcda36ad912 100644 --- a/chromium/components/payments/content/utility/payment_manifest_parser_unittest.cc +++ b/chromium/components/payments/content/utility/payment_manifest_parser_unittest.cc @@ -24,8 +24,9 @@ void ExpectUnableToParsePaymentMethodManifest(const std::string& input) { std::unique_ptr<base::Value> value = base::JSONReader::ReadDeprecated(input); PaymentManifestParser::ParsePaymentMethodManifestIntoVectors( - std::move(value), ErrorLogger(), &actual_web_app_urls, - &actual_supported_origins, &actual_all_origins_supported); + GURL("https://bobpay.com/pmm.json"), std::move(value), ErrorLogger(), + &actual_web_app_urls, &actual_supported_origins, + &actual_all_origins_supported); EXPECT_TRUE(actual_web_app_urls.empty()) << actual_web_app_urls.front(); EXPECT_TRUE(actual_supported_origins.empty()) @@ -45,8 +46,9 @@ void ExpectParsedPaymentMethodManifest( std::unique_ptr<base::Value> value = base::JSONReader::ReadDeprecated(input); PaymentManifestParser::ParsePaymentMethodManifestIntoVectors( - std::move(value), ErrorLogger(), &actual_web_app_urls, - &actual_supported_origins, &actual_all_origins_supported); + GURL("https://bobpay.com/pmm.json"), std::move(value), ErrorLogger(), + &actual_web_app_urls, &actual_supported_origins, + &actual_all_origins_supported); EXPECT_EQ(expected_web_app_urls, actual_web_app_urls); EXPECT_EQ(expected_supported_origins, actual_supported_origins); @@ -91,9 +93,10 @@ TEST(PaymentManifestParserTest, ListOfEmptyDefaultApplicationsIsMalformed) { "{\"default_applications\": [\"\"]}"); } -TEST(PaymentManifestParserTest, RelativeURLDefaultApplicationIsMalformed) { - ExpectUnableToParsePaymentMethodManifest( - "{\"default_applications\": [\"manifest.json\"]}"); +TEST(PaymentManifestParserTest, DefaultApplicationCanBeRelativeURL) { + ExpectParsedPaymentMethodManifest( + "{\"default_applications\": [\"manifest.json\"]}", + {GURL("https://bobpay.com/manifest.json")}, {}, false); } TEST(PaymentManifestParserTest, DefaultApplicationsShouldNotHaveNulCharacters) { @@ -111,11 +114,15 @@ TEST(PaymentManifestParserTest, DefaultApplicationKeyShouldBeLowercase) { "{\"Default_Applications\": [\"https://bobpay.com/app.json\"]}"); } -TEST(PaymentManifestParserTest, DefaultApplicationsShouldBeAbsoluteUrls) { - ExpectUnableToParsePaymentMethodManifest( +TEST(PaymentManifestParserTest, + DefaultApplicationsCanBeEitherAbsoluteOrRelative) { + ExpectParsedPaymentMethodManifest( "{\"default_applications\": [" - "\"https://bobpay.com/app.json\"," - "\"app.json\"]}"); + "\"https://bobpay.com/app1.json\"," + "\"app2.json\"]}", + {GURL("https://bobpay.com/app1.json"), + GURL("https://bobpay.com/app2.json")}, + {}, false); } TEST(PaymentManifestParserTest, DefaultApplicationsShouldBeHttps) { diff --git a/chromium/components/payments/content/utility/payment_method_manifest_parser_fuzzer.cc b/chromium/components/payments/content/utility/payment_method_manifest_parser_fuzzer.cc index 266b487524f..6245c7a6ec4 100644 --- a/chromium/components/payments/content/utility/payment_method_manifest_parser_fuzzer.cc +++ b/chromium/components/payments/content/utility/payment_method_manifest_parser_fuzzer.cc @@ -39,7 +39,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { payments::ErrorLogger log; log.DisableInTest(); payments::PaymentManifestParser::ParsePaymentMethodManifestIntoVectors( - std::move(value), log, &web_app_manifest_urls, &supported_origins, - &all_origins_supported); + GURL("https://chromium.org/pmm.json"), std::move(value), log, + &web_app_manifest_urls, &supported_origins, &all_origins_supported); return 0; } diff --git a/chromium/components/payments/core/BUILD.gn b/chromium/components/payments/core/BUILD.gn index 0f55c095ed4..084d14c4722 100644 --- a/chromium/components/payments/core/BUILD.gn +++ b/chromium/components/payments/core/BUILD.gn @@ -12,6 +12,8 @@ jumbo_static_library("core") { "currency_formatter.h", "error_logger.cc", "error_logger.h", + "error_message_util.cc", + "error_message_util.h", "features.cc", "features.h", "journey_logger.cc", @@ -38,6 +40,8 @@ jumbo_static_library("core") { "payment_prefs.h", "payment_shipping_option.cc", "payment_shipping_option.h", + "payments_experimental_features.cc", + "payments_experimental_features.h", "payments_validators.cc", "payments_validators.h", "url_util.cc", @@ -63,8 +67,6 @@ jumbo_static_library("core") { "payment_request_delegate.h", "payment_response.cc", "payment_response.h", - "payments_experimental_features.cc", - "payments_experimental_features.h", "payments_profile_comparator.cc", "payments_profile_comparator.h", "strings_util.cc", @@ -171,7 +173,6 @@ jumbo_source_set("unit_tests") { "payment_request_data_util_unittest.cc", "payment_response_unittest.cc", "payments_profile_comparator_unittest.cc", - "strings_util_unittest.cc", "web_payment_request_unittest.cc", ] } @@ -197,99 +198,3 @@ jumbo_source_set("unit_tests") { "//ui/base", ] } - -if (is_ios) { - bundle_data("payments_test_bundle_data") { - testonly = true - - sources = [ - "//components/test/data/payments/abort.js", - "//components/test/data/payments/alicepay_bobpay_charliepay_and_cards.js", - "//components/test/data/payments/blob_url.js", - "//components/test/data/payments/bobpay.js", - "//components/test/data/payments/bobpay_and_basic_card_with_modifier_optional_data.js", - "//components/test/data/payments/bobpay_and_basic_card_with_modifiers.js", - "//components/test/data/payments/bobpay_and_cards.js", - "//components/test/data/payments/bobpay_ui_skip.js", - "//components/test/data/payments/bobpay_ui_skip_preload.js", - "//components/test/data/payments/can_make_payment_metrics.js", - "//components/test/data/payments/can_make_payment_query.js", - "//components/test/data/payments/can_make_payment_query_bobpay.js", - "//components/test/data/payments/can_make_payment_query_cc.js", - "//components/test/data/payments/contact_details.js", - "//components/test/data/payments/contact_details_and_free_shipping.js", - "//components/test/data/payments/debit.js", - "//components/test/data/payments/dynamic_shipping.js", - "//components/test/data/payments/email.js", - "//components/test/data/payments/email_and_free_shipping.js", - "//components/test/data/payments/email_and_phone.js", - "//components/test/data/payments/extra_shipping_options.js", - "//components/test/data/payments/fail_complete.js", - "//components/test/data/payments/free_shipping.js", - "//components/test/data/payments/initiated.js", - "//components/test/data/payments/initiated_test.html", - "//components/test/data/payments/long_id.js", - "//components/test/data/payments/metrics.js", - "//components/test/data/payments/modifier.js", - "//components/test/data/payments/multiple_show.js", - "//components/test/data/payments/name.js", - "//components/test/data/payments/name_and_free_shipping.js", - "//components/test/data/payments/no_shipping.js", - "//components/test/data/payments/payment_method_identifier.js", - "//components/test/data/payments/payment_request.html", - "//components/test/data/payments/payment_request.js", - "//components/test/data/payments/payment_request_abort_test.html", - "//components/test/data/payments/payment_request_alicepay_bobpay_charliepay_and_cards_test.html", - "//components/test/data/payments/payment_request_blob_url_test.html", - "//components/test/data/payments/payment_request_bobpay_and_basic_card_with_modifier_optional_data_test.html", - "//components/test/data/payments/payment_request_bobpay_and_basic_card_with_modifiers_test.html", - "//components/test/data/payments/payment_request_bobpay_and_cards_test.html", - "//components/test/data/payments/payment_request_bobpay_test.html", - "//components/test/data/payments/payment_request_bobpay_ui_skip_preload_test.html", - "//components/test/data/payments/payment_request_bobpay_ui_skip_test.html", - "//components/test/data/payments/payment_request_can_make_payment_metrics_test.html", - "//components/test/data/payments/payment_request_can_make_payment_query_bobpay_test.html", - "//components/test/data/payments/payment_request_can_make_payment_query_cc_test.html", - "//components/test/data/payments/payment_request_can_make_payment_query_test.html", - "//components/test/data/payments/payment_request_contact_details_and_free_shipping_test.html", - "//components/test/data/payments/payment_request_contact_details_test.html", - "//components/test/data/payments/payment_request_debit_test.html", - "//components/test/data/payments/payment_request_dynamic_shipping_test.html", - "//components/test/data/payments/payment_request_email_and_free_shipping_test.html", - "//components/test/data/payments/payment_request_email_and_phone_test.html", - "//components/test/data/payments/payment_request_email_test.html", - "//components/test/data/payments/payment_request_extra_shipping_options_test.html", - "//components/test/data/payments/payment_request_fail_complete_test.html", - "//components/test/data/payments/payment_request_free_shipping_test.html", - "//components/test/data/payments/payment_request_id.js", - "//components/test/data/payments/payment_request_id_test.html", - "//components/test/data/payments/payment_request_iframe.html", - "//components/test/data/payments/payment_request_long_id_test.html", - "//components/test/data/payments/payment_request_main.html", - "//components/test/data/payments/payment_request_metrics_test.html", - "//components/test/data/payments/payment_request_modifier_test.html", - "//components/test/data/payments/payment_request_multiple_requests.html", - "//components/test/data/payments/payment_request_multiple_show_test.html", - "//components/test/data/payments/payment_request_name_and_free_shipping_test.html", - "//components/test/data/payments/payment_request_name_test.html", - "//components/test/data/payments/payment_request_no_shipping_test.html", - "//components/test/data/payments/payment_request_payment_method_identifier_test.html", - "//components/test/data/payments/payment_request_phone_and_free_shipping_test.html", - "//components/test/data/payments/payment_request_phone_test.html", - "//components/test/data/payments/payment_request_shipping_address_change_test.html", - "//components/test/data/payments/payment_request_show_promise.html", - "//components/test/data/payments/payment_request_show_twice_test.html", - "//components/test/data/payments/phone.js", - "//components/test/data/payments/phone_and_free_shipping.js", - "//components/test/data/payments/shipping_address_change.js", - "//components/test/data/payments/show_promise.js", - "//components/test/data/payments/show_twice.js", - "//components/test/data/payments/style.css", - "//components/test/data/payments/util.js", - ] - outputs = [ - "{{bundle_resources_dir}}/" + - "{{source_root_relative_dir}}/{{source_file_part}}", - ] - } -} diff --git a/chromium/components/payments/core/OWNERS b/chromium/components/payments/core/OWNERS deleted file mode 100644 index a54ecb4e8c0..00000000000 --- a/chromium/components/payments/core/OWNERS +++ /dev/null @@ -1,4 +0,0 @@ -per-file journey_logger*=sebsg@chromium.org -per-file address_normalizer*=sebsg@chromium.org - -# COMPONENT: UI>Browser>Payments diff --git a/chromium/components/payments/core/autofill_payment_app.cc b/chromium/components/payments/core/autofill_payment_app.cc index 57c34c781a5..72391991696 100644 --- a/chromium/components/payments/core/autofill_payment_app.cc +++ b/chromium/components/payments/core/autofill_payment_app.cc @@ -33,7 +33,6 @@ namespace payments { AutofillPaymentApp::AutofillPaymentApp( const std::string& method_name, const autofill::CreditCard& card, - bool matches_merchant_card_type_exactly, const std::vector<autofill::AutofillProfile*>& billing_profiles, const std::string& app_locale, PaymentRequestBaseDelegate* payment_request_delegate) @@ -42,7 +41,6 @@ AutofillPaymentApp::AutofillPaymentApp( PaymentApp::Type::AUTOFILL), method_name_(method_name), credit_card_(card), - matches_merchant_card_type_exactly_(matches_merchant_card_type_exactly), billing_profiles_(billing_profiles), app_locale_(app_locale), delegate_(nullptr), @@ -95,11 +93,7 @@ uint32_t AutofillPaymentApp::GetCompletenessScore() const { } bool AutofillPaymentApp::CanPreselect() const { - return IsCompleteForPayment() && matches_merchant_card_type_exactly_; -} - -bool AutofillPaymentApp::IsExactlyMatchingMerchantRequest() const { - return matches_merchant_card_type_exactly_; + return IsCompleteForPayment(); } base::string16 AutofillPaymentApp::GetMissingInfoLabel() const { @@ -107,7 +101,7 @@ base::string16 AutofillPaymentApp::GetMissingInfoLabel() const { GetCompletionStatusForCard(credit_card_, app_locale_, billing_profiles_)); } -bool AutofillPaymentApp::IsValidForCanMakePayment() const { +bool AutofillPaymentApp::HasEnrolledInstrument() const { CreditCardCompletionStatus status = GetCompletionStatusForCard(credit_card_, app_locale_, billing_profiles_); if (PaymentsExperimentalFeatures::IsEnabled( @@ -128,6 +122,11 @@ void AutofillPaymentApp::RecordUse() { credit_card_); } +bool AutofillPaymentApp::NeedsInstallation() const { + // Autofill payment app is built-in, so it doesn't need installation. + return false; +} + base::string16 AutofillPaymentApp::GetLabel() const { return credit_card_.NetworkAndLastFourDigits(); } @@ -140,31 +139,12 @@ base::string16 AutofillPaymentApp::GetSublabel() const { bool AutofillPaymentApp::IsValidForModifier( const std::string& method, bool supported_networks_specified, - const std::set<std::string>& supported_networks, - bool supported_types_specified, - const std::set<autofill::CreditCard::CardType>& supported_types) const { + const std::set<std::string>& supported_networks) const { bool is_valid = false; IsValidForPaymentMethodIdentifier(method, &is_valid); if (!is_valid) return false; - // If supported_types is not specified and this instrument matches the method, - // the modifier is applicable. If supported_types is populated, it must - // contain this card's type to be applicable. The same is true for - // supported_networks. - if (supported_types_specified) { - // supported_types may contain CARD_TYPE_UNKNOWN because of the parsing - // function so the local card only matches if it's because the website - // didn't specify types (meaning they don't care). - if (credit_card_.card_type() == - autofill::CreditCard::CardType::CARD_TYPE_UNKNOWN) { - return false; - } - - if (supported_types.find(credit_card_.card_type()) == supported_types.end()) - return false; - } - if (supported_networks_specified) { std::string basic_card_network = autofill::data_util::GetPaymentRequestData(credit_card_.network()) @@ -219,18 +199,12 @@ void AutofillPaymentApp::OnFullCardRequestFailed() { void AutofillPaymentApp::RecordMissingFieldsForApp() const { CreditCardCompletionStatus completion_status = GetCompletionStatusForCard(credit_card_, app_locale_, billing_profiles_); - if (completion_status == CREDIT_CARD_COMPLETE && - matches_merchant_card_type_exactly_) { + if (completion_status == CREDIT_CARD_COMPLETE) return; - } - // Record cases that the card type does not match the requested type(s) in - // addititon to missing fields from card completion status. - base::UmaHistogramSparse( - "PaymentRequest.MissingPaymentFields", - completion_status | - (matches_merchant_card_type_exactly_ ? 0 - : CREDIT_CARD_TYPE_MISMATCH)); + // Record the missing fields from card completion status. + base::UmaHistogramSparse("PaymentRequest.MissingPaymentFields", + completion_status); } void AutofillPaymentApp::GenerateBasicCardResponse() { diff --git a/chromium/components/payments/core/autofill_payment_app.h b/chromium/components/payments/core/autofill_payment_app.h index 365daef0655..c84102570af 100644 --- a/chromium/components/payments/core/autofill_payment_app.h +++ b/chromium/components/payments/core/autofill_payment_app.h @@ -32,7 +32,6 @@ class AutofillPaymentApp AutofillPaymentApp( const std::string& method_name, const autofill::CreditCard& card, - bool matches_merchant_card_type_exactly, const std::vector<autofill::AutofillProfile*>& billing_profiles, const std::string& app_locale, PaymentRequestBaseDelegate* payment_request_delegate); @@ -43,18 +42,16 @@ class AutofillPaymentApp bool IsCompleteForPayment() const override; uint32_t GetCompletenessScore() const override; bool CanPreselect() const override; - bool IsExactlyMatchingMerchantRequest() const override; base::string16 GetMissingInfoLabel() const override; - bool IsValidForCanMakePayment() const override; + bool HasEnrolledInstrument() const override; void RecordUse() override; + bool NeedsInstallation() 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, - bool supported_types_specified, - const std::set<autofill::CreditCard::CardType>& - supported_types) 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; @@ -93,12 +90,6 @@ class AutofillPaymentApp // A copy of the card is owned by this object. autofill::CreditCard credit_card_; - // Whether the card type (credit, debit, prepaid) matches the merchant's - // requested card type exactly. If the merchant accepts all card types, then - // this variable is always "true". If the merchant accepts only a subset of - // the card types, then this variable is "false" for unknown card types. - const bool matches_merchant_card_type_exactly_; - // Not owned by this object, should outlive this. const std::vector<autofill::AutofillProfile*>& billing_profiles_; diff --git a/chromium/components/payments/core/autofill_payment_app_unittest.cc b/chromium/components/payments/core/autofill_payment_app_unittest.cc index 4a44c794770..fb69076325b 100644 --- a/chromium/components/payments/core/autofill_payment_app_unittest.cc +++ b/chromium/components/payments/core/autofill_payment_app_unittest.cc @@ -155,9 +155,8 @@ class AutofillPaymentAppTest : public testing::Test { // A valid local credit card is a valid app for payment. TEST_F(AutofillPaymentAppTest, IsCompleteForPayment) { - AutofillPaymentApp app("visa", local_credit_card(), - /*matches_merchant_card_type_exactly=*/true, - billing_profiles(), "en-US", nullptr); + AutofillPaymentApp app("visa", local_credit_card(), billing_profiles(), + "en-US", nullptr); EXPECT_TRUE(app.IsCompleteForPayment()); EXPECT_TRUE(app.GetMissingInfoLabel().empty()); } @@ -166,9 +165,7 @@ TEST_F(AutofillPaymentAppTest, IsCompleteForPayment) { TEST_F(AutofillPaymentAppTest, IsCompleteForPayment_Expired) { autofill::CreditCard& card = local_credit_card(); card.SetExpirationYear(2016); // Expired. - AutofillPaymentApp app("visa", card, - /*matches_merchant_card_type_exactly=*/true, - billing_profiles(), "en-US", nullptr); + AutofillPaymentApp app("visa", card, billing_profiles(), "en-US", nullptr); EXPECT_TRUE(app.IsCompleteForPayment()); EXPECT_EQ(base::string16(), app.GetMissingInfoLabel()); } @@ -179,9 +176,7 @@ TEST_F(AutofillPaymentAppTest, IsCompleteForPayment_NoName) { card.SetInfo(autofill::AutofillType(autofill::CREDIT_CARD_NAME_FULL), base::ASCIIToUTF16(""), "en-US"); base::string16 missing_info; - AutofillPaymentApp app("visa", card, - /*matches_merchant_card_type_exactly=*/true, - billing_profiles(), "en-US", nullptr); + 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()); @@ -192,9 +187,7 @@ TEST_F(AutofillPaymentAppTest, IsCompleteForPayment_NoNumber) { autofill::CreditCard& card = local_credit_card(); card.SetNumber(base::ASCIIToUTF16("")); base::string16 missing_info; - AutofillPaymentApp app("visa", card, - /*matches_merchant_card_type_exactly=*/true, - billing_profiles(), "en-US", nullptr); + 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), @@ -207,9 +200,7 @@ TEST_F(AutofillPaymentAppTest, IsCompleteForPayment_NoBillinbAddressId) { autofill::CreditCard& card = local_credit_card(); card.set_billing_address_id(""); base::string16 missing_info; - AutofillPaymentApp app("visa", card, - /*matches_merchant_card_type_exactly=*/true, - billing_profiles(), "en-US", nullptr); + AutofillPaymentApp app("visa", card, billing_profiles(), "en-US", nullptr); EXPECT_FALSE(app.IsCompleteForPayment()); EXPECT_EQ( l10n_util::GetStringUTF16(IDS_PAYMENTS_CARD_BILLING_ADDRESS_REQUIRED), @@ -222,9 +213,7 @@ 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, - /*matches_merchant_card_type_exactly=*/true, - billing_profiles(), "en-US", nullptr); + AutofillPaymentApp app("visa", card, billing_profiles(), "en-US", nullptr); EXPECT_FALSE(app.IsCompleteForPayment()); EXPECT_EQ( l10n_util::GetStringUTF16(IDS_PAYMENTS_CARD_BILLING_ADDRESS_REQUIRED), @@ -240,9 +229,7 @@ TEST_F(AutofillPaymentAppTest, IsCompleteForPayment_IncompleteBillinbAddress) { autofill::CreditCard& card = local_credit_card(); card.set_billing_address_id(incomplete_profile.guid()); base::string16 missing_info; - AutofillPaymentApp app("visa", card, - /*matches_merchant_card_type_exactly=*/true, - billing_profiles(), "en-US", nullptr); + AutofillPaymentApp app("visa", card, billing_profiles(), "en-US", nullptr); EXPECT_FALSE(app.IsCompleteForPayment()); EXPECT_EQ( l10n_util::GetStringUTF16(IDS_PAYMENTS_CARD_BILLING_ADDRESS_REQUIRED), @@ -256,9 +243,7 @@ TEST_F(AutofillPaymentAppTest, IsCompleteForPayment_MultipleThingsMissing) { card.SetNumber(base::ASCIIToUTF16("")); card.SetInfo(autofill::AutofillType(autofill::CREDIT_CARD_NAME_FULL), base::ASCIIToUTF16(""), "en-US"); - AutofillPaymentApp app("visa", card, - /*matches_merchant_card_type_exactly=*/true, - billing_profiles(), "en-US", nullptr); + 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()); @@ -269,9 +254,7 @@ 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, - /*matches_merchant_card_type_exactly=*/true, - billing_profiles(), "en-US", nullptr); + AutofillPaymentApp app("visa", card, billing_profiles(), "en-US", nullptr); EXPECT_TRUE(app.IsCompleteForPayment()); EXPECT_TRUE(app.GetMissingInfoLabel().empty()); } @@ -282,52 +265,42 @@ TEST_F(AutofillPaymentAppTest, IsCompleteForPayment_ExpiredMaskedCard) { ASSERT_GT(billing_profiles().size(), 0UL); card.set_billing_address_id(billing_profiles()[0]->guid()); card.SetExpirationYear(2016); // Expired. - AutofillPaymentApp app("visa", card, - /*matches_merchant_card_type_exactly=*/true, - billing_profiles(), "en-US", nullptr); + 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, IsValidForCanMakePayment_Minimal) { +TEST_F(AutofillPaymentAppTest, HasEnrolledInstrument_Minimal) { autofill::CreditCard& card = local_credit_card(); card.SetExpirationYear(2016); // Expired. - AutofillPaymentApp app("visa", card, - /*matches_merchant_card_type_exactly=*/true, - billing_profiles(), "en-US", nullptr); - EXPECT_TRUE(app.IsValidForCanMakePayment()); + 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, IsValidForCanMakePayment_MaskedCard) { +TEST_F(AutofillPaymentAppTest, HasEnrolledInstrument_MaskedCard) { autofill::CreditCard card = autofill::test::GetMaskedServerCard(); card.SetExpirationYear(2016); // Expired. - AutofillPaymentApp app("visa", card, - /*matches_merchant_card_type_exactly=*/true, - billing_profiles(), "en-US", nullptr); - EXPECT_TRUE(app.IsValidForCanMakePayment()); + 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, IsValidForCanMakePayment_NoName) { +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, - /*matches_merchant_card_type_exactly=*/true, - billing_profiles(), "en-US", nullptr); - EXPECT_FALSE(app.IsValidForCanMakePayment()); + 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, IsValidForCanMakePayment_NoNumber) { +TEST_F(AutofillPaymentAppTest, HasEnrolledInstrument_NoNumber) { autofill::CreditCard& card = local_credit_card(); card.SetNumber(base::ASCIIToUTF16("")); - AutofillPaymentApp app("visa", card, - /*matches_merchant_card_type_exactly=*/true, - billing_profiles(), "en-US", nullptr); - EXPECT_FALSE(app.IsValidForCanMakePayment()); + AutofillPaymentApp app("visa", card, billing_profiles(), "en-US", nullptr); + EXPECT_FALSE(app.HasEnrolledInstrument()); } // Tests that the autofill app only calls OnappDetailsReady when @@ -341,9 +314,7 @@ TEST_F(AutofillPaymentAppTest, InvokePaymentApp_NormalizationBeforeUnmask) { autofill::CreditCard& card = local_credit_card(); card.SetNumber(base::ASCIIToUTF16("")); - AutofillPaymentApp app("visa", card, - /*matches_merchant_card_type_exactly=*/true, - billing_profiles(), "en-US", &delegate); + AutofillPaymentApp app("visa", card, billing_profiles(), "en-US", &delegate); FakePaymentAppDelegate app_delegate; @@ -371,9 +342,7 @@ TEST_F(AutofillPaymentAppTest, InvokePaymentApp_UnmaskBeforeNormalization) { autofill::CreditCard& card = local_credit_card(); card.SetNumber(base::ASCIIToUTF16("")); - AutofillPaymentApp app("visa", card, - /*matches_merchant_card_type_exactly=*/true, - billing_profiles(), "en-US", &delegate); + AutofillPaymentApp app("visa", card, billing_profiles(), "en-US", &delegate); FakePaymentAppDelegate app_delegate; diff --git a/chromium/components/payments/core/error_message_util.cc b/chromium/components/payments/core/error_message_util.cc new file mode 100644 index 00000000000..0bd30d292a1 --- /dev/null +++ b/chromium/components/payments/core/error_message_util.cc @@ -0,0 +1,35 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/payments/core/error_message_util.h" + +#include <algorithm> +#include <vector> + +#include "base/logging.h" +#include "base/strings/string_util.h" +#include "components/payments/core/native_error_strings.h" + +namespace payments { + +std::string GetNotSupportedErrorMessage(const std::set<std::string>& methods) { + if (methods.empty()) + return errors::kGenericPaymentMethodNotSupportedMessage; + + std::vector<std::string> with_quotes(methods.size()); + std::transform( + methods.begin(), methods.end(), with_quotes.begin(), + [](const std::string& method_name) { return "\"" + method_name + "\""; }); + + std::string output; + bool replaced = base::ReplaceChars( + with_quotes.size() == 1 + ? errors::kSinglePaymentMethodNotSupportedFormat + : errors::kMultiplePaymentMethodsNotSupportedFormat, + "$", base::JoinString(with_quotes, ", "), &output); + DCHECK(replaced); + return output; +} + +} // namespace payments diff --git a/chromium/components/payments/core/error_message_util.h b/chromium/components/payments/core/error_message_util.h new file mode 100644 index 00000000000..a99870480a9 --- /dev/null +++ b/chromium/components/payments/core/error_message_util.h @@ -0,0 +1,19 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_PAYMENTS_CORE_ERROR_MESSAGE_UTIL_H_ +#define COMPONENTS_PAYMENTS_CORE_ERROR_MESSAGE_UTIL_H_ + +#include <set> +#include <string> + +namespace payments { + +// Returns a developer-facing error message that the given payment |methods| are +// not supported. +std::string GetNotSupportedErrorMessage(const std::set<std::string>& methods); + +} // namespace payments + +#endif // COMPONENTS_PAYMENTS_CORE_ERROR_MESSAGE_UTIL_H_ diff --git a/chromium/components/payments/core/error_strings.cc b/chromium/components/payments/core/error_strings.cc index a411d7ad739..ccf6dfa96d0 100644 --- a/chromium/components/payments/core/error_strings.cc +++ b/chromium/components/payments/core/error_strings.cc @@ -17,7 +17,6 @@ const char kCannotShowTwice[] = "Attempted show twice."; const char kCannotShowWithoutInit[] = "Attempted show without initialization."; const char kCannotUpdateWithoutInit[] = "Attempted updateWith without initialization."; const char kCannotUpdateWithoutShow[] = "Attempted updateWith without show."; -const char kGenericPaymentMethodNotSupportedMessage[] = "Payment method not supported."; const char kInvalidState[] = "Invalid state."; const char kNotInASecureOrigin[] = "Not in a secure origin."; const char kProhibitedOrigin[] = "Only localhost, file://, and cryptographic scheme origins allowed."; diff --git a/chromium/components/payments/core/error_strings.h b/chromium/components/payments/core/error_strings.h index ced992e0b56..7187e4d8b7b 100644 --- a/chromium/components/payments/core/error_strings.h +++ b/chromium/components/payments/core/error_strings.h @@ -35,9 +35,6 @@ extern const char kCannotUpdateWithoutInit[]; // Mojo call PaymentRequest::Show() must precede PaymentRequest::UpdateWith(). extern const char kCannotUpdateWithoutShow[]; -// A message about unsupported payment method. -extern const char kGenericPaymentMethodNotSupportedMessage[]; - // Used when an invalid state is encountered generically. extern const char kInvalidState[]; diff --git a/chromium/components/payments/core/features.cc b/chromium/components/payments/core/features.cc index 865507a2b0e..684aaa8bea7 100644 --- a/chromium/components/payments/core/features.cc +++ b/chromium/components/payments/core/features.cc @@ -53,8 +53,8 @@ const base::Feature kPaymentRequestSkipToGPay{ const base::Feature kPaymentRequestSkipToGPayIfNoCard{ "PaymentRequestSkipToGPayIfNoCard", base::FEATURE_DISABLED_BY_DEFAULT}; -const base::Feature kWebPaymentMicrotransaction{ - "WebPaymentMicrotransaction", base::FEATURE_DISABLED_BY_DEFAULT}; +const base::Feature kDownRankJustInTimePaymentApp{ + "DownRankJustInTimePaymentApp", base::FEATURE_DISABLED_BY_DEFAULT}; } // namespace features } // namespace payments diff --git a/chromium/components/payments/core/features.h b/chromium/components/payments/core/features.h index 8cdade4a77b..4b492b143ff 100644 --- a/chromium/components/payments/core/features.h +++ b/chromium/components/payments/core/features.h @@ -60,8 +60,9 @@ extern const base::Feature kPaymentRequestSkipToGPay; // eligible credit card. extern const base::Feature kPaymentRequestSkipToGPayIfNoCard; -// Controls whether the microtransaction features are enabled. -extern const base::Feature kWebPaymentMicrotransaction; +// If enabled, just-in-time installable payment handlers are ranked lower than +// complete autofill instruments in payment sheet's method selection section. +extern const base::Feature kDownRankJustInTimePaymentApp; } // namespace features } // namespace payments diff --git a/chromium/components/payments/core/journey_logger.cc b/chromium/components/payments/core/journey_logger.cc index 572d4886af8..92e7b958cff 100644 --- a/chromium/components/payments/core/journey_logger.cc +++ b/chromium/components/payments/core/journey_logger.cc @@ -80,19 +80,13 @@ void RecordTimeToCheckoutUmaHistograms(const std::string name, base::TimeDelta::FromMinutes(5) /* max */, 100 /*bucket count*/); } -enum class TransactionSize { - kZeroTransaction = 0, - kMicroTransaction = 1, - kRegularTransaction = 2, - kMaxValue = kRegularTransaction, -}; - } // namespace -JourneyLogger::JourneyLogger(bool is_incognito, ukm::SourceId source_id) +JourneyLogger::JourneyLogger(bool is_incognito, + ukm::SourceId payment_request_source_id) : is_incognito_(is_incognito), events_(EVENT_INITIATED), - source_id_(source_id) {} + payment_request_source_id_(payment_request_source_id) {} JourneyLogger::~JourneyLogger() { // has_recorded_ is false in cases that the page gets closed. To see more @@ -259,6 +253,15 @@ void JourneyLogger::RecordTransactionAmount(std::string currency, transaction_size = TransactionSize::kMicroTransaction; base::UmaHistogramEnumeration( "PaymentRequest.TransactionAmount" + completion_suffix, transaction_size); + + if (payment_request_source_id_ == ukm::kInvalidSourceId) + return; + + // Record the transaction amount in UKM. + ukm::builders::PaymentRequest_TransactionAmount(payment_request_source_id_) + .SetCompletionStatus(completed) + .SetCategory(static_cast<int64_t>(transaction_size)) + .Record(ukm::UkmRecorder::Get()); } void JourneyLogger::RecordJourneyStatsHistograms( @@ -351,14 +354,26 @@ void JourneyLogger::RecordEventsMetric(CompletionStatus completion_status) { ValidateEventBits(); base::UmaHistogramSparse("PaymentRequest.Events", events_); - if (source_id_ == ukm::kInvalidSourceId) + if (payment_request_source_id_ == ukm::kInvalidSourceId) return; // Record the events in UKM. - ukm::builders::PaymentRequest_CheckoutEvents(source_id_) + ukm::builders::PaymentRequest_CheckoutEvents(payment_request_source_id_) + .SetCompletionStatus(completion_status) + .SetEvents(events_) + .Record(ukm::UkmRecorder::Get()); + + if (payment_app_source_id_ == ukm::kInvalidSourceId) + return; + + // Record the events in UKM for payment app. + ukm::builders::PaymentApp_CheckoutEvents(payment_app_source_id_) .SetCompletionStatus(completion_status) .SetEvents(events_) .Record(ukm::UkmRecorder::Get()); + + // Clear payment app source id since it gets deleted after recording. + payment_app_source_id_ = ukm::kInvalidSourceId; } void JourneyLogger::RecordTimeToCheckout( @@ -488,4 +503,9 @@ void JourneyLogger::SetTriggerTime() { trigger_time_ = base::TimeTicks::Now(); } +void JourneyLogger::SetPaymentAppUkmSourceId( + ukm::SourceId payment_app_source_id) { + payment_app_source_id_ = payment_app_source_id; +} + } // namespace payments diff --git a/chromium/components/payments/core/journey_logger.h b/chromium/components/payments/core/journey_logger.h index 02f8d941ec7..47cf99c4d0c 100644 --- a/chromium/components/payments/core/journey_logger.h +++ b/chromium/components/payments/core/journey_logger.h @@ -140,7 +140,19 @@ class JourneyLogger { NOT_SHOWN_REASON_MAX = 4, }; - JourneyLogger(bool is_incognito, ukm::SourceId source_id); + // Transactions fall in one of the following categories after converting to + // USD. + enum class TransactionSize { + // 0$ transactions. + kZeroTransaction = 0, + // Transaction value <= 1$. + kMicroTransaction = 1, + // Transaction value > 1$. + kRegularTransaction = 2, + kMaxValue = kRegularTransaction, + }; + + JourneyLogger(bool is_incognito, ukm::SourceId payment_request_source_id); ~JourneyLogger(); // Increments the number of selection adds for the specified section. @@ -203,6 +215,9 @@ class JourneyLogger { // Records when Payment Request .show is called. void SetTriggerTime(); + // Sets the ukm source id of the selected app when it gets invoked. + void SetPaymentAppUkmSourceId(ukm::SourceId payment_app_source_id); + private: static const int NUMBER_OF_SECTIONS = 3; @@ -273,7 +288,8 @@ class JourneyLogger { // checkout duration. base::TimeTicks trigger_time_; - ukm::SourceId source_id_; + ukm::SourceId payment_request_source_id_; + ukm::SourceId payment_app_source_id_ = ukm::kInvalidSourceId; DISALLOW_COPY_AND_ASSIGN(JourneyLogger); }; diff --git a/chromium/components/payments/core/method_strings.cc b/chromium/components/payments/core/method_strings.cc index 38d0b9e1e5f..177d810616c 100644 --- a/chromium/components/payments/core/method_strings.cc +++ b/chromium/components/payments/core/method_strings.cc @@ -14,6 +14,7 @@ namespace methods { const char kAndroidPay[] = "https://android.com/pay"; const char kBasicCard[] = "basic-card"; const char kGooglePay[] = "https://google.com/pay"; +const char kGooglePlayBilling[] = "https://play.google.com/billing"; const char kInterledger[] = "interledger"; const char kPayeeCreditTransfer[] = "payee-credit-transfer"; const char kPayerCreditTransfer[] = "payer-credit-transfer"; diff --git a/chromium/components/payments/core/method_strings.h b/chromium/components/payments/core/method_strings.h index a16179808be..25f97e8b6cb 100644 --- a/chromium/components/payments/core/method_strings.h +++ b/chromium/components/payments/core/method_strings.h @@ -25,6 +25,9 @@ extern const char kBasicCard[]; // https://developers.google.com/pay/api/web/guides/tutorial extern const char kGooglePay[]; +// Google Play Billing method name. +extern const char kGooglePlayBilling[]; + // Interledger method name. // https://w3c.github.io/webpayments/proposals/interledger/ extern const char kInterledger[]; diff --git a/chromium/components/payments/core/native_error_strings.cc b/chromium/components/payments/core/native_error_strings.cc index bc579579735..d45bad6fc3b 100644 --- a/chromium/components/payments/core/native_error_strings.cc +++ b/chromium/components/payments/core/native_error_strings.cc @@ -53,9 +53,6 @@ const char kCrossOriginWebAppManifestNotAllowed[] = const char kDetailedInvalidSslCertificateMessageFormat[] = "SSL certificate is not valid. Security level: $."; -const char kHttpHeadRequestFailed[] = - "Unable to make a HEAD request to \"$1\" for payment method manifest."; - const char kHttpStatusCodeNotAllowed[] = "HTTP status code $1 \"$2\" not allowed for payment method manifest " "\"$3\"."; @@ -64,6 +61,9 @@ const char kInstallingMultipleDefaultAppsNotSupported[] = "Installing multiple payment handlers from a single payment method " "manifest is not supported."; +const char kInvalidInitiatorFrame[] = + "Cannot initialize PaymentRequest in an invalid frame."; + const char kInvalidManifestUrl[] = "\"$1\" is not a valid payment manifest URL with HTTPS scheme (or HTTP " "scheme for localhost)."; @@ -76,6 +76,10 @@ const char kInvalidServiceWorkerUrl[] = const char kInvalidSslCertificate[] = "SSL certificate is not valid."; +const char kInvalidWebAppIcon[] = + "Failed to download or decode a non-empty icon for payment app with \"$1\" " + "manifest."; + const char kMethodDataRequired[] = "Method data required."; const char kMethodNameRequired[] = "Method name required."; @@ -89,9 +93,6 @@ const char kMissingMethodNameFromPaymentApp[] = const char kMultiplePaymentMethodsNotSupportedFormat[] = "The payment methods $ are not supported."; -const char kNoLinkRelPaymentMethodManifestHttpHeader[] = - "No \"Link: rel=payment-method-manifest\" HTTP header found at \"$1\"."; - const char kNoResponseToPaymentEvent[] = "Payment handler did not respond to \"paymentrequest\" event."; @@ -163,5 +164,69 @@ const char kShippingAddressInvalid[] = const char kShippingOptionEmpty[] = "Payment app returned invalid response. Missing field \"shipping option\"."; +const char kCanMakePaymentEventRejected[] = + "Payment handler rejected the promise passed into " + "CanMakePaymentEvent.respondWith()."; + +const char kCanMakePaymentEventTimeout[] = + "The \"canmakepayment\" event timed out."; + +const char kCanMakePaymentEventNoResponse[] = + "Payment handler did not respond to \"canmakepayment\" event."; + +const char kCanMakePaymentEventNoReadyForMinimalUiValue[] = + "Payment handler did not specify a value for \"readyForMinimalUI\" in " + "CanMakePaymentEvent.respondWithMinimalUI().."; + +const char kCanMakePaymentEventBooleanConversionError[] = + "Unable to convert the value of \"canmakepayment\" response to a boolean."; + +const char kCanMakePaymentEventBrowserError[] = + "Browser encountered an error when firing the \"canmakepayment\" event in " + "the payment handler."; + +const char kCanMakePaymentEventInternalError[] = + "Payment handler encountered an error (e.g., threw a JavaScript exception) " + "while responding to \"canmakepayment\" event."; + +const char kCanMakePaymentEventInvalidAccountBalanceValue[] = + "Payment handler provided invalid account balance value in " + "CanMakePaymentEvent.respondWithMinimalUI()."; + +const char kCanMakePaymentEventMinimalUiResponseConversionError[] = + "Unable to parse the object that the payment handler passed into " + "CanMakePaymentEvent.respondWithMinimalUI()."; + +const char kCanMakePaymentEventNoAccountBalanceValue[] = + "Payment handler did not specify account balance in " + "CanMakePaymentEvent.respondWithMinimalUI()."; + +const char kCanMakePaymentEventNoCanMakePaymentValue[] = + "Payment handler did not specify a value for \"canMakePayment\" in " + "CanMakePaymentEvent.respondWithMinimalUI()."; + +const char kCanMakePaymentEventNoUrlBasedPaymentMethods[] = + "Browser did not fire \"canmakepayment\" event because the payment handler " + "does not support any URL-based payment methods."; + +const char kCanMakePaymentEventNotInstalled[] = + "Browser did not fire \"canmakepayment\" event because the payment handler " + "is not yet installed. It will be installed on demand when the user " + "selects it."; + +const char kCanMakePaymentEventNoExplicitlyVerifiedMethods[] = + "Browser did not fire \"canmakepayment\" event because the payment handler " + "does not support any explicitly verified payment methods."; + +const char kGenericPaymentMethodNotSupportedMessage[] = + "Payment method not supported."; + +const char kNoContentAndNoLinkHeader[] = + "No content and no \"Link: rel=payment-method-manifest\" HTTP header found " + "at \"$1\"."; + +const char kNoContentInPaymentManifest[] = + "No content found in payment manifest \"$1\"."; + } // namespace errors } // namespace payments diff --git a/chromium/components/payments/core/native_error_strings.h b/chromium/components/payments/core/native_error_strings.h index fb234c3439d..f723ff4338b 100644 --- a/chromium/components/payments/core/native_error_strings.h +++ b/chromium/components/payments/core/native_error_strings.h @@ -71,10 +71,6 @@ extern const char kCrossOriginWebAppManifestNotAllowed[]; // to replace. extern const char kDetailedInvalidSslCertificateMessageFormat[]; -// Used when a HEAD request for URL A fails. This format should be used with -// base::ReplaceStringPlaceholders(fmt, {A}, nullptr). -extern const char kHttpHeadRequestFailed[]; - // Used for HTTP redirects that are prohibited for payment method manifests. // This format should be used with base::ReplaceStringPlaceholders(fmt, // {http_code, http_code_phrase, original_url}, nullptr). @@ -84,6 +80,10 @@ extern const char kHttpStatusCodeNotAllowed[]; // install feature to work. extern const char kInstallingMultipleDefaultAppsNotSupported[]; +// PaymentRequest::Init() is called when the initiating RenderFrameHost no +// longer exists. +extern const char kInvalidInitiatorFrame[]; + // Used to let the web developer know about an invalid payment manifest URL A. // This format should be used with base::ReplaceStringPlaceholders(fmt, {A}, // nullptr). @@ -99,6 +99,10 @@ extern const char kInvalidServiceWorkerUrl[]; // invalid SSL certificate. extern const char kInvalidSslCertificate[]; +// The downloaded web app icon should draw something for JIT install feature to +// work. +extern const char kInvalidWebAppIcon[]; + // Used when the {"supportedMethods": "", data: {}} is required, but not // provided. extern const char kMethodDataRequired[]; @@ -117,11 +121,6 @@ extern const char kMissingMethodNameFromPaymentApp[]; // where "$" is the character to replace. extern const char kMultiplePaymentMethodsNotSupportedFormat[]; -// Used when the payment method URL A does not have a "Link: -// rel=payment-method-manifest" HTTP header. This format should be used with -// base::ReplaceStringPlaceholders(fmt, {A}, nullptr). -extern const char kNoLinkRelPaymentMethodManifestHttpHeader[]; - // Payment handler did not respond to the "paymentrequest" event. extern const char kNoResponseToPaymentEvent[]; @@ -196,6 +195,68 @@ extern const char kShippingAddressInvalid[]; // The payment handler responded with an empty "shipping option" field. extern const char kShippingOptionEmpty[]; +// The payment handler rejected the promise passed into +// CanMakePaymentEvent.respondWith(). +extern const char kCanMakePaymentEventRejected[]; + +// The payment handler timed out responding to "canmakepayment" event. +extern const char kCanMakePaymentEventTimeout[]; + +// The payment handler did not respond to the "canmakepayment" event. +extern const char kCanMakePaymentEventNoResponse[]; + +// The payment handler did not specify a value for "readyForMinimalUI" field. +extern const char kCanMakePaymentEventNoReadyForMinimalUiValue[]; + +// The payment handler called CanMakePaymentEvent.respondWith(value) with a +// non-boolean value. +extern const char kCanMakePaymentEventBooleanConversionError[]; + +// Browser encountered an error when firing the "canmakepayment" event. +extern const char kCanMakePaymentEventBrowserError[]; + +// The payment handler threw a JavaScript exception while handling the +// "canmakepayment" event. +extern const char kCanMakePaymentEventInternalError[]; + +// The payment handler specified an invalid value for "accountBalance". +extern const char kCanMakePaymentEventInvalidAccountBalanceValue[]; + +// The payment handler called CanMakePaymentEvent.respondWithMinimalUI(value) +// with a value that could not be converted into a JavaScript dictionary with +// values for "canMakePayment", "readyForMinimalUI", and "accountBalance". +extern const char kCanMakePaymentEventMinimalUiResponseConversionError[]; + +// The payment handler did not specify a value for "accountBalance". +extern const char kCanMakePaymentEventNoAccountBalanceValue[]; + +// The payment handler did not specify a value for "canMakePayment" field in +// CanMakePaymentEvent.respondWithMinimalUI(). +extern const char kCanMakePaymentEventNoCanMakePaymentValue[]; + +// Browser does not fire the "canmakepayment" event if the payment handler does +// not support any URL-based payment methods. +extern const char kCanMakePaymentEventNoUrlBasedPaymentMethods[]; + +// Browser does not fire the "canmakepayment" event for just-in-time installable +// payment handlers. +extern const char kCanMakePaymentEventNotInstalled[]; + +// Browser fires the "canmakepayment" event only for explicitly verified payment +// methods, i.e., not when "supportedOrigins": "*". +extern const char kCanMakePaymentEventNoExplicitlyVerifiedMethods[]; + +// A message about unsupported payment method. +extern const char kGenericPaymentMethodNotSupportedMessage[]; + +// Used for errors downloading the payment method manifest. This format should +// be used with base::ReplaceStringPlaceholders(fmt, {A}, nullptr). +extern const char kNoContentAndNoLinkHeader[]; + +// User when the downloaded payment manifest A is empty. This format should be +// used with base::ReplaceStringPlaceholders(fmt, {A}, nullptr). +extern const char kNoContentInPaymentManifest[]; + } // namespace errors } // namespace payments diff --git a/chromium/components/payments/core/payment_app.cc b/chromium/components/payments/core/payment_app.cc index f0a162ddcfe..6cfe02e7225 100644 --- a/chromium/components/payments/core/payment_app.cc +++ b/chromium/components/payments/core/payment_app.cc @@ -8,8 +8,40 @@ #include "components/autofill/core/common/autofill_clock.h" #include "components/payments/core/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. Installed 3rd-party payment handlers +// 2. Complete autofill instruments +// 3. Just-in-time installable payment handlers that is not yet installed. +// 4. Incomplete autofill instruments +int GetSortingGroup(const PaymentApp& app) { + switch (app.type()) { + 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 3; + } + return 1; + break; + case PaymentApp::Type::AUTOFILL: + if (app.IsCompleteForPayment()) { + return 2; + } else { + return 4; + } + } +} +} // namespace PaymentApp::PaymentApp(int icon_resource_id, Type type) : icon_resource_id_(icon_resource_id), type_(type) {} @@ -30,6 +62,10 @@ const std::set<std::string>& PaymentApp::GetAppMethodNames() const { return app_method_names_; } +ukm::SourceId PaymentApp::UkmSourceId() { + return ukm::kInvalidSourceId; +} + // static void PaymentApp::SortApps(std::vector<std::unique_ptr<PaymentApp>>* apps) { DCHECK(apps); @@ -47,25 +83,21 @@ void PaymentApp::SortApps(std::vector<PaymentApp*>* apps) { } bool PaymentApp::operator<(const PaymentApp& other) const { - // Non-autofill apps before autofill. - if (type_ == Type::AUTOFILL && other.type() != Type::AUTOFILL) - return false; - if (type_ != Type::AUTOFILL && other.type() == Type::AUTOFILL) - return true; + 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; - // Among equally complete cards, those with matching type come before unknown - // type cards. - if (IsExactlyMatchingMerchantRequest() != - other.IsExactlyMatchingMerchantRequest()) { - return IsExactlyMatchingMerchantRequest(); - } - // Sort autofill cards using their frecency scores as tie breaker. if (type_ == Type::AUTOFILL) { DCHECK_EQ(other.type(), Type::AUTOFILL); diff --git a/chromium/components/payments/core/payment_app.h b/chromium/components/payments/core/payment_app.h index e4563d85a32..5cf771dce2a 100644 --- a/chromium/components/payments/core/payment_app.h +++ b/chromium/components/payments/core/payment_app.h @@ -15,6 +15,7 @@ #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 "ui/gfx/image/image_skia.h" namespace payments { @@ -57,19 +58,15 @@ class PaymentApp { // the apps can be preselected, the user must explicitly select an app from a // list. virtual bool CanPreselect() const = 0; - // Returns whether the app is exactly matching all filters provided by the - // merchant. For example, this can return "false" for unknown autofill card - // types, if the merchant requested only debit cards from "basic-card". - virtual bool IsExactlyMatchingMerchantRequest() 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 whether the app is valid for the purposes of responding to - // canMakePayment. - // TODO(crbug.com/915907): rename to IsValidForHasEnrolledInstrument. - virtual bool IsValidForCanMakePayment() 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; // 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; @@ -77,16 +74,13 @@ class PaymentApp { // 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, supported_types, etc) 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. + // 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, - bool supported_types_specified, - const std::set<autofill::CreditCard::CardType>& supported_types) - const = 0; + 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 @@ -117,6 +111,8 @@ class PaymentApp { int icon_resource_id() const { return icon_resource_id_; } Type type() const { return type_; } + virtual ukm::SourceId UkmSourceId(); + protected: PaymentApp(int icon_resource_id, Type type); diff --git a/chromium/components/payments/core/payment_manifest_downloader.cc b/chromium/components/payments/core/payment_manifest_downloader.cc index 21d9d82eb9a..b1728535284 100644 --- a/chromium/components/payments/core/payment_manifest_downloader.cc +++ b/chromium/components/payments/core/payment_manifest_downloader.cc @@ -12,6 +12,7 @@ #include "base/optional.h" #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "components/link_header_util/link_header_util.h" @@ -33,59 +34,29 @@ namespace payments { namespace { -GURL ParseResponseHeader(const GURL& url, - scoped_refptr<net::HttpResponseHeaders> headers, - const ErrorLogger& log, - std::string* out_error_message) { - if (!headers) { - *out_error_message = base::ReplaceStringPlaceholders( - errors::kNoLinkRelPaymentMethodManifestHttpHeader, {url.spec()}, - nullptr); - log.Error(*out_error_message); - return GURL(); - } - - int response_code = headers->response_code(); - if (response_code != net::HTTP_OK && response_code != net::HTTP_NO_CONTENT) { - *out_error_message = base::ReplaceStringPlaceholders( - errors::kHttpHeadRequestFailed, {url.spec()}, nullptr), - log.Error(*out_error_message); - return GURL(); - } - - std::string link_header; - headers->GetNormalizedHeader("link", &link_header); - if (link_header.empty()) { - *out_error_message = base::ReplaceStringPlaceholders( - errors::kNoLinkRelPaymentMethodManifestHttpHeader, {url.spec()}, - nullptr); - log.Error(*out_error_message); - return GURL(); - } - - for (const auto& value : link_header_util::SplitLinkHeader(link_header)) { - std::string payment_method_manifest_url; - std::unordered_map<std::string, base::Optional<std::string>> params; - if (!link_header_util::ParseLinkHeaderValue( - value.first, value.second, &payment_method_manifest_url, ¶ms)) { - continue; - } - - auto rel = params.find("rel"); - if (rel == params.end()) - continue; +// Invokes |callback| with |error_format|. +void RespondWithError(const base::StringPiece& error_format, + const GURL& final_url, + const ErrorLogger& log, + PaymentManifestDownloadCallback callback) { + std::string error_message = base::ReplaceStringPlaceholders( + error_format, {final_url.spec()}, nullptr); + log.Error(error_message); + std::move(callback).Run(final_url, std::string(), error_message); +} - std::vector<std::string> rel_parts = - base::SplitString(rel->second.value_or(""), HTTP_LWS, - base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); - if (base::Contains(rel_parts, "payment-method-manifest")) - return url.Resolve(payment_method_manifest_url); +// Invokes the |callback| with |response_body|. If |response_body| is empty, +// then invokes |callback| with |empty_error_format|. +void RespondWithContent(const std::string& response_body, + const base::StringPiece& empty_error_format, + const GURL& final_url, + const ErrorLogger& log, + PaymentManifestDownloadCallback callback) { + if (response_body.empty()) { + RespondWithError(empty_error_format, final_url, log, std::move(callback)); + } else { + std::move(callback).Run(final_url, response_body, std::string()); } - - *out_error_message = base::ReplaceStringPlaceholders( - errors::kNoLinkRelPaymentMethodManifestHttpHeader, {url.spec()}, nullptr); - log.Error(*out_error_message); - return GURL(); } bool IsValidManifestUrl(const GURL& url, @@ -126,22 +97,6 @@ GURL ParseRedirectUrl(const net::RedirectInfo& redirect_info, return redirect_info.new_url; } -std::string ParseResponseContent( - const GURL& final_url, - const std::string& response_body, - scoped_refptr<net::HttpResponseHeaders> headers, - const ErrorLogger& log, - std::string* out_error_message) { - if (!headers || headers->response_code() != net::HTTP_OK) { - *out_error_message = base::ReplaceStringPlaceholders( - errors::kPaymentManifestDownloadFailed, {final_url.spec()}, nullptr); - log.Error(*out_error_message); - return std::string(); - } - - return response_body; -} - } // namespace PaymentManifestDownloader::PaymentManifestDownloader( @@ -155,20 +110,24 @@ PaymentManifestDownloader::PaymentManifestDownloader( PaymentManifestDownloader::~PaymentManifestDownloader() {} void PaymentManifestDownloader::DownloadPaymentMethodManifest( + const url::Origin& merchant_origin, const GURL& url, PaymentManifestDownloadCallback callback) { DCHECK(UrlUtil::IsValidManifestUrl(url)); // Restrict number of redirects for efficiency and breaking circle. - InitiateDownload(url, "HEAD", + InitiateDownload(merchant_origin, url, + Download::Type::RESPONSE_BODY_OR_LINK_HEADER, /*allowed_number_of_redirects=*/3, std::move(callback)); } void PaymentManifestDownloader::DownloadWebAppManifest( + const url::Origin& payment_method_manifest_origin, const GURL& url, PaymentManifestDownloadCallback callback) { DCHECK(UrlUtil::IsValidManifestUrl(url)); - InitiateDownload(url, "GET", /*allowed_number_of_redirects=*/0, - std::move(callback)); + InitiateDownload(payment_method_manifest_origin, url, + Download::Type::RESPONSE_BODY, + /*allowed_number_of_redirects=*/0, std::move(callback)); } GURL PaymentManifestDownloader::FindTestServerURL(const GURL& url) const { @@ -193,7 +152,7 @@ void PaymentManifestDownloader::OnURLLoaderRedirect( // Manually follow some type of redirects. std::string error_message; if (download->allowed_number_of_redirects > 0) { - DCHECK(download->method == "HEAD"); + DCHECK_EQ(Download::Type::RESPONSE_BODY_OR_LINK_HEADER, download->type); GURL redirect_url = ParseRedirectUrl(redirect_info, download->original_url, *log_, &error_message); if (!redirect_url.is_empty()) { @@ -201,7 +160,9 @@ void PaymentManifestDownloader::OnURLLoaderRedirect( if (net::registry_controlled_domains::SameDomainOrHost( download->original_url, redirect_url, net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)) { - InitiateDownload(redirect_url, "HEAD", + // Redirects preserve the original request initiator. + InitiateDownload(download->request_initiator, redirect_url, + Download::Type::RESPONSE_BODY_OR_LINK_HEADER, --download->allowed_number_of_redirects, std::move(download->callback)); return; @@ -247,38 +208,88 @@ void PaymentManifestDownloader::OnURLLoaderCompleteInternal( downloads_.erase(download_it); std::string error_message; - if (download->method == "GET") { - std::string content = ParseResponseContent(final_url, response_body, - headers, *log_, &error_message); - std::move(download->callback).Run(final_url, content, error_message); + if (download->type == Download::Type::RESPONSE_BODY) { + if (!headers || headers->response_code() != net::HTTP_OK) { + RespondWithError(errors::kPaymentManifestDownloadFailed, final_url, *log_, + std::move(download->callback)); + } else { + RespondWithContent(response_body, errors::kNoContentInPaymentManifest, + final_url, *log_, std::move(download->callback)); + } return; } - DCHECK_EQ("HEAD", download->method); - GURL payment_method_manifest_url = - ParseResponseHeader(final_url, headers, *log_, &error_message); - if (!payment_method_manifest_url.is_valid()) { - std::move(download->callback).Run(final_url, std::string(), error_message); + DCHECK_EQ(Download::Type::RESPONSE_BODY_OR_LINK_HEADER, download->type); + + if (!headers) { + RespondWithContent(response_body, errors::kNoContentAndNoLinkHeader, + final_url, *log_, std::move(download->callback)); return; } - if (!IsValidManifestUrl(payment_method_manifest_url, *log_, &error_message)) { - std::move(download->callback).Run(final_url, std::string(), error_message); + if (headers->response_code() != net::HTTP_OK && + headers->response_code() != net::HTTP_NO_CONTENT) { + RespondWithError(errors::kPaymentManifestDownloadFailed, final_url, *log_, + std::move(download->callback)); return; } - if (!url::IsSameOriginWith(final_url, payment_method_manifest_url)) { - error_message = base::ReplaceStringPlaceholders( - errors::kCrossOriginPaymentMethodManifestNotAllowed, - {payment_method_manifest_url.spec(), final_url.spec()}, nullptr); - log_->Error(error_message); - std::move(download->callback).Run(final_url, std::string(), error_message); + std::string link_header; + headers->GetNormalizedHeader("link", &link_header); + if (link_header.empty()) { + RespondWithContent(response_body, errors::kNoContentAndNoLinkHeader, + final_url, *log_, std::move(download->callback)); return; } - InitiateDownload(payment_method_manifest_url, "GET", - /*allowed_number_of_redirects=*/0, - std::move(download->callback)); + for (const auto& value : link_header_util::SplitLinkHeader(link_header)) { + std::string link_url; + std::unordered_map<std::string, base::Optional<std::string>> params; + if (!link_header_util::ParseLinkHeaderValue(value.first, value.second, + &link_url, ¶ms)) { + continue; + } + + auto rel = params.find("rel"); + if (rel == params.end()) + continue; + + std::vector<std::string> rel_parts = + base::SplitString(rel->second.value_or(""), HTTP_LWS, + base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + if (base::Contains(rel_parts, "payment-method-manifest")) { + GURL payment_method_manifest_url = final_url.Resolve(link_url); + + if (!IsValidManifestUrl(payment_method_manifest_url, *log_, + &error_message)) { + std::move(download->callback) + .Run(final_url, std::string(), error_message); + return; + } + + if (!url::IsSameOriginWith(final_url, payment_method_manifest_url)) { + error_message = base::ReplaceStringPlaceholders( + errors::kCrossOriginPaymentMethodManifestNotAllowed, + {payment_method_manifest_url.spec(), final_url.spec()}, nullptr); + log_->Error(error_message); + std::move(download->callback) + .Run(final_url, std::string(), error_message); + return; + } + + // The request initiator for the payment method manifest is the origin of + // the GET request with the HTTP link header. + // https://github.com/w3c/webappsec-fetch-metadata/issues/30 + InitiateDownload( + url::Origin::Create(final_url), payment_method_manifest_url, + Download::Type::RESPONSE_BODY, + /*allowed_number_of_redirects=*/0, std::move(download->callback)); + return; + } + } + + RespondWithContent(response_body, errors::kNoContentAndNoLinkHeader, + final_url, *log_, std::move(download->callback)); } network::SimpleURLLoader* PaymentManifestDownloader::GetLoaderForTesting() { @@ -292,12 +303,18 @@ GURL PaymentManifestDownloader::GetLoaderOriginalURLForTesting() { } void PaymentManifestDownloader::InitiateDownload( + const url::Origin& request_initiator, const GURL& url, - const std::string& method, + Download::Type download_type, int allowed_number_of_redirects, PaymentManifestDownloadCallback callback) { DCHECK(UrlUtil::IsValidManifestUrl(url)); + // Only initial download of the payment method manifest (which might contain + // an HTTP Link header) is allowed to redirect. + DCHECK(allowed_number_of_redirects == 0 || + download_type == Download::Type::RESPONSE_BODY_OR_LINK_HEADER); + net::NetworkTrafficAnnotationTag traffic_annotation = net::DefineNetworkTrafficAnnotation("payment_manifest_downloader", R"( semantics { @@ -319,8 +336,9 @@ void PaymentManifestDownloader::InitiateDownload( policy_exception_justification: "Not implemented." })"); auto resource_request = std::make_unique<network::ResourceRequest>(); + resource_request->request_initiator = request_initiator; resource_request->url = url; - resource_request->method = method; + resource_request->method = "GET"; resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit; std::unique_ptr<network::SimpleURLLoader> loader = network::SimpleURLLoader::Create(std::move(resource_request), @@ -334,7 +352,8 @@ void PaymentManifestDownloader::InitiateDownload( weak_ptr_factory_.GetWeakPtr(), loader.get())); auto download = std::make_unique<Download>(); - download->method = method; + download->request_initiator = request_initiator; + download->type = download_type; download->original_url = url; download->loader = std::move(loader); download->callback = std::move(callback); diff --git a/chromium/components/payments/core/payment_manifest_downloader.h b/chromium/components/payments/core/payment_manifest_downloader.h index 099d5cebbf6..b638801bfd0 100644 --- a/chromium/components/payments/core/payment_manifest_downloader.h +++ b/chromium/components/payments/core/payment_manifest_downloader.h @@ -16,6 +16,7 @@ #include "base/memory/weak_ptr.h" #include "services/network/public/mojom/url_response_head.mojom-forward.h" #include "url/gurl.h" +#include "url/origin.h" namespace net { class HttpResponseHeaders; @@ -36,19 +37,24 @@ class ErrorLogger; // // Download failure results in empty contents. Failure to download the manifest // can happen because of the following reasons: -// - HTTP response code is not 200. (204 is also allowed for HEAD request.) -// - HTTP GET on the manifest URL returns empty content. +// - HTTP response code is not 200. (204 is also allowed for payment method +// manifest.) // // In the case of a payment method manifest download, can also fail when: // - More than three redirects. // - Cross-site redirects. -// - HTTP response headers are absent. -// - HTTP response headers do not contain Link headers. -// - Link header does not contain rel="payment-method-manifest". -// - Link header does not contain a valid URL of the same origin. +// - HTTP GET on the manifest URL returns empty content and: +// - HTTP response headers are absent. +// - HTTP response headers do not contain Link headers. +// - Link header does not contain rel="payment-method-manifest". +// - Link header does not contain a valid URL of the same origin. +// - After following the Link header: +// - There's a redirect. +// - HTTP GET returns empty content. // // In the case of a web app manifest download, can also also fail when: // - There's a redirect. +// - HTTP GET on the manifest URL returns empty content. using PaymentManifestDownloadCallback = base::OnceCallback<void(const GURL& url, const std::string& contents, @@ -58,9 +64,9 @@ using PaymentManifestDownloadCallback = // payment method name that is a URL with HTTPS scheme, e.g., // https://bobpay.com. // -// The downloader follows up to three redirects for the HEAD request only (used -// for payment method manifests). Three is enough for known legitimate use cases -// and seems like a good upper bound. +// The downloader follows up to three redirects for the payment method manifest +// request only. Three is enough for known legitimate use cases and seems like a +// good upper bound. // // The command line must be initialized to use this class in tests, because it // checks for --unsafely-treat-insecure-origin-as-secure=<origin> flag. For @@ -74,34 +80,43 @@ class PaymentManifestDownloader { virtual ~PaymentManifestDownloader(); - // Download a payment method manifest via two consecutive HTTP requests: - // - // 1) HEAD request for the payment method name. The HTTP response header is - // parsed for Link header that points to the location of the payment method - // manifest file. Example of a relative location: + // Download a payment method manifest from |url| via a GET. The HTTP response + // header is parsed for Link header. If there is no Link header, then the body + // is returned. If there's a Link header, then it is followed exactly once. + // Example header: // // Link: <data/payment-manifest.json>; rel="payment-method-manifest" // - // (This is relative to the payment method URL.) Example of an absolute - // location: + // (This is relative to the payment method URL.) Example of an absolute + // location: // // Link: <https://bobpay.com/data/payment-manifest.json>; // rel="payment-method-manifest" // - // The absolute location must use HTTPS scheme. + // The absolute location must use HTTPS scheme. // - // 2) GET request for the payment method manifest file. + // |merchant_origin| should be 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'. // - // |url| should be a valid URL with HTTPS scheme. - void DownloadPaymentMethodManifest(const GURL& url, + // |url| should be valid according to UrlUtil::IsValidManifestUrl() to + // download. + void DownloadPaymentMethodManifest(const url::Origin& merchant_origin, + const GURL& url, PaymentManifestDownloadCallback callback); - // Download a web app manifest via a single HTTP request: + // Download a web app manifest from |url| via a single HTTP request: // // 1) GET request for the payment method name. // - // |url| should be a valid URL with HTTPS scheme. - void DownloadWebAppManifest(const GURL& url, + // |payment_method_manifest_origin| should be the origin of the payment method + // manifest that is pointing to this web app manifest. It is used for security + // features like 'Sec-Fetch-Site' and 'Cross-Origin-Resource-Policy'. + // + // |url| should be valid according to UrlUtil::IsValidManifestUrl() to + // download. + void DownloadWebAppManifest(const url::Origin& payment_method_manifest_origin, + const GURL& url, PaymentManifestDownloadCallback callback); // Overridden in TestDownloader to convert |url| to a test server URL. The @@ -115,11 +130,17 @@ class PaymentManifestDownloader { // Information about an ongoing download request. struct Download { + enum class Type { + RESPONSE_BODY_OR_LINK_HEADER, + RESPONSE_BODY, + }; + Download(); ~Download(); int allowed_number_of_redirects = 0; - std::string method; + Type type = Type::RESPONSE_BODY; + url::Origin request_initiator; GURL original_url; std::unique_ptr<network::SimpleURLLoader> loader; PaymentManifestDownloadCallback callback; @@ -150,8 +171,9 @@ class PaymentManifestDownloader { GURL GetLoaderOriginalURLForTesting(); // Overridden in TestDownloader. - virtual void InitiateDownload(const GURL& url, - const std::string& method, + virtual void InitiateDownload(const url::Origin& request_initiator, + const GURL& url, + Download::Type download_type, int allowed_number_of_redirects, PaymentManifestDownloadCallback callback); diff --git a/chromium/components/payments/core/payment_manifest_downloader_unittest.cc b/chromium/components/payments/core/payment_manifest_downloader_unittest.cc index d60c654298f..2fa43962b36 100644 --- a/chromium/components/payments/core/payment_manifest_downloader_unittest.cc +++ b/chromium/components/payments/core/payment_manifest_downloader_unittest.cc @@ -22,12 +22,20 @@ namespace { using testing::_; -static const char kNoError[] = ""; +static constexpr char kNoContent[] = ""; +static constexpr char kNoError[] = ""; +static constexpr char kNoLinkHeader[] = ""; +static constexpr char kNoResponseBody[] = ""; } // namespace class PaymentMethodManifestDownloaderTest : public testing::Test { - public: + protected: + enum class Headers { + kSend, + kOmit, + }; + PaymentMethodManifestDownloaderTest() : test_url_("https://bobpay.com"), shared_url_loader_factory_( @@ -36,40 +44,39 @@ class PaymentMethodManifestDownloaderTest : public testing::Test { downloader_(std::make_unique<ErrorLogger>(), shared_url_loader_factory_) { downloader_.DownloadPaymentMethodManifest( - test_url_, + url::Origin::Create(GURL("https://chromium.org")), test_url_, base::BindOnce(&PaymentMethodManifestDownloaderTest::OnManifestDownload, base::Unretained(this))); } - ~PaymentMethodManifestDownloaderTest() override {} - MOCK_METHOD3(OnManifestDownload, void(const GURL& unused_url_after_redirects, const std::string& content, const std::string& error_message)); - void CallComplete(int response_code = 200, - const std::string& link_header = std::string(), - const std::string& response_body = std::string(), - bool send_headers = true) { + void ServerResponse(int response_code, + Headers send_headers, + const std::string& link_header, + const std::string& response_body) { scoped_refptr<net::HttpResponseHeaders> headers; - if (send_headers) { + if (send_headers == Headers::kSend) { headers = base::MakeRefCounted<net::HttpResponseHeaders>(std::string()); headers->ReplaceStatusLine(base::StringPrintf( "HTTP/1.1 %d %s", response_code, net::GetHttpReasonPhrase( static_cast<net::HttpStatusCode>(response_code)))); + + if (!link_header.empty()) + headers->AddHeader(link_header); } - if (!link_header.empty()) - headers->AddHeader(link_header); downloader_.OnURLLoaderCompleteInternal( downloader_.GetLoaderForTesting(), downloader_.GetLoaderOriginalURLForTesting(), response_body, headers, net::OK); } - void CallRedirect(int redirect_code, const GURL& new_url) { + void ServerRedirect(int redirect_code, const GURL& new_url) { net::RedirectInfo redirect_info; redirect_info.status_code = redirect_code; redirect_info.new_url = new_url; @@ -88,127 +95,247 @@ class PaymentMethodManifestDownloaderTest : public testing::Test { network::TestURLLoaderFactory test_factory_; scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory_; PaymentManifestDownloader downloader_; - - DISALLOW_COPY_AND_ASSIGN(PaymentMethodManifestDownloaderTest); }; -TEST_F(PaymentMethodManifestDownloaderTest, HttpHeadResponse404IsFailure) { +TEST_F(PaymentMethodManifestDownloaderTest, FirstHttpResponse404IsFailure) { + EXPECT_CALL( + *this, + OnManifestDownload( + _, kNoContent, + "Unable to download payment manifest \"https://bobpay.com/\".")); + + ServerResponse(404, Headers::kSend, kNoLinkHeader, kNoResponseBody); +} + +TEST_F(PaymentMethodManifestDownloaderTest, + NoHttpHeadersAndEmptyResponseBodyIsFailure) { EXPECT_CALL(*this, OnManifestDownload( - _, std::string(), - "Unable to make a HEAD request to \"https://bobpay.com/\" " - "for payment method manifest.")); + _, kNoContent, + "No content and no \"Link: rel=payment-method-manifest\" " + "HTTP header found at \"https://bobpay.com/\".")); - CallComplete(404); + ServerResponse(200, Headers::kOmit, kNoLinkHeader, kNoResponseBody); } -TEST_F(PaymentMethodManifestDownloaderTest, NoHttpHeadersIsFailure) { - EXPECT_CALL( - *this, OnManifestDownload(_, std::string(), - "No \"Link: rel=payment-method-manifest\" HTTP " - "header found at \"https://bobpay.com/\".")); +TEST_F(PaymentMethodManifestDownloaderTest, + NoHttpHeadersButWithResponseBodyIsSuccess) { + EXPECT_CALL(*this, OnManifestDownload(_, "response body", kNoError)); - CallComplete(200, std::string(), std::string(), false); + ServerResponse(200, Headers::kOmit, kNoLinkHeader, "response body"); } -TEST_F(PaymentMethodManifestDownloaderTest, EmptyHttpHeaderIsFailure) { +TEST_F(PaymentMethodManifestDownloaderTest, + EmptyHttpHeaderAndEmptyResponseBodyIsFailure) { EXPECT_CALL( - *this, OnManifestDownload(_, std::string(), - "No \"Link: rel=payment-method-manifest\" HTTP " - "header found at \"https://bobpay.com/\".")); + *this, OnManifestDownload( + _, kNoContent, + "No content and no \"Link: rel=payment-method-manifest\" HTTP " + "header found at \"https://bobpay.com/\".")); - CallComplete(200); + ServerResponse(200, Headers::kSend, kNoLinkHeader, kNoResponseBody); } -TEST_F(PaymentMethodManifestDownloaderTest, EmptyHttpLinkHeaderIsFailure) { - scoped_refptr<net::HttpResponseHeaders> headers( - new net::HttpResponseHeaders(std::string())); - EXPECT_CALL( - *this, OnManifestDownload(_, std::string(), - "No \"Link: rel=payment-method-manifest\" HTTP " - "header found at \"https://bobpay.com/\".")); +TEST_F(PaymentMethodManifestDownloaderTest, + EmptyHttpHeaderButWithResponseBodyIsSuccess) { + EXPECT_CALL(*this, OnManifestDownload(_, "response content", kNoError)); - CallComplete(200, "Link:"); + ServerResponse(200, Headers::kSend, kNoLinkHeader, "response content"); } -TEST_F(PaymentMethodManifestDownloaderTest, NoRelInHttpLinkHeaderIsFailure) { - EXPECT_CALL( - *this, OnManifestDownload(_, std::string(), - "No \"Link: rel=payment-method-manifest\" HTTP " - "header found at \"https://bobpay.com/\".")); +TEST_F(PaymentMethodManifestDownloaderTest, + EmptyHttpLinkHeaderWithoutResponseBodyIsFailure) { + EXPECT_CALL(*this, + OnManifestDownload( + _, kNoContent, + "No content and no \"Link: rel=payment-method-manifest\" " + "HTTP header found at \"https://bobpay.com/\".")); - CallComplete(200, "Link: <manifest.json>"); + ServerResponse(200, Headers::kSend, "Link:", kNoResponseBody); } -TEST_F(PaymentMethodManifestDownloaderTest, NoUrlInHttpLinkHeaderIsFailure) { - EXPECT_CALL( - *this, OnManifestDownload(_, std::string(), - "No \"Link: rel=payment-method-manifest\" HTTP " - "header found at \"https://bobpay.com/\".")); +TEST_F(PaymentMethodManifestDownloaderTest, + EmptyHttpLinkHeaderButWithResponseBodyIsSuccess) { + EXPECT_CALL(*this, OnManifestDownload(_, "response body", kNoError)); - CallComplete(200, "Link: rel=payment-method-manifest"); + ServerResponse(200, Headers::kSend, "Link:", "response body"); } TEST_F(PaymentMethodManifestDownloaderTest, - NoManifestRellInHttpLinkHeaderIsFailure) { - EXPECT_CALL( - *this, OnManifestDownload(_, std::string(), - "No \"Link: rel=payment-method-manifest\" HTTP " - "header found at \"https://bobpay.com/\".")); + NoRelInHttpLinkHeaderAndNoResponseBodyIsFailure) { + EXPECT_CALL(*this, + OnManifestDownload( + _, std::string(), + "No content and no \"Link: rel=payment-method-manifest\" " + "HTTP header found at \"https://bobpay.com/\".")); - CallComplete(200, "Link: <manifest.json>; rel=web-app-manifest"); + ServerResponse(200, Headers::kSend, "Link: <manifest.json>", kNoResponseBody); } -TEST_F(PaymentMethodManifestDownloaderTest, HttpGetResponse404IsFailure) { - scoped_refptr<net::HttpResponseHeaders> headers( - new net::HttpResponseHeaders(std::string())); - CallComplete(200, "Link: <manifest.json>; rel=payment-method-manifest"); +TEST_F(PaymentMethodManifestDownloaderTest, + NoRelInHttpLinkHeaderButWithResponseBodyIsSuccess) { + EXPECT_CALL(*this, OnManifestDownload(_, "response body", kNoError)); + + ServerResponse(200, Headers::kSend, "Link: <manifest.json>", "response body"); +} +TEST_F(PaymentMethodManifestDownloaderTest, + NoUrlInHttpLinkHeaderAndNoResponseBodyIsFailure) { EXPECT_CALL(*this, - OnManifestDownload(_, std::string(), + OnManifestDownload( + _, kNoContent, + "No content and no \"Link: rel=payment-method-manifest\" " + "HTTP header found at \"https://bobpay.com/\".")); + + ServerResponse(200, Headers::kSend, "Link: rel=payment-method-manifest", + kNoResponseBody); +} + +TEST_F(PaymentMethodManifestDownloaderTest, + NoUrlInHttpLinkHeaderButWithResponseBodyIsSuccess) { + EXPECT_CALL(*this, OnManifestDownload(_, "response body", kNoError)); + + ServerResponse(200, Headers::kSend, "Link: rel=payment-method-manifest", + "response body"); +} + +TEST_F(PaymentMethodManifestDownloaderTest, + NoManifestRellInHttpLinkHeaderAndNoResponseBodyIsFailure) { + EXPECT_CALL(*this, + OnManifestDownload( + _, kNoContent, + "No content and no \"Link: rel=payment-method-manifest\" " + "HTTP header found at \"https://bobpay.com/\".")); + + ServerResponse(200, Headers::kSend, + "Link: <manifest.json>; rel=web-app-manifest", + kNoResponseBody); +} + +TEST_F(PaymentMethodManifestDownloaderTest, + NoManifestRellInHttpLinkHeaderButWithResponseBodyIsSuccess) { + EXPECT_CALL(*this, OnManifestDownload(_, "response body", kNoError)); + + ServerResponse(200, Headers::kSend, + "Link: <manifest.json>; rel=web-app-manifest", + "response body"); +} + +TEST_F(PaymentMethodManifestDownloaderTest, SecondHttpResponse404IsFailure) { + ServerResponse(200, Headers::kSend, + "Link: <manifest.json>; rel=payment-method-manifest", + kNoResponseBody); + + EXPECT_CALL(*this, + OnManifestDownload(_, kNoContent, "Unable to download payment manifest " "\"https://bobpay.com/manifest.json\".")); - CallComplete(404); + ServerResponse(404, Headers::kSend, kNoLinkHeader, kNoResponseBody); } -TEST_F(PaymentMethodManifestDownloaderTest, EmptyHttpGetResponseIsFailure) { - CallComplete(200, "Link: <manifest.json>; rel=payment-method-manifest"); +TEST_F(PaymentMethodManifestDownloaderTest, EmptySecondResponseIsFailure) { + ServerResponse(200, Headers::kSend, + "Link: <manifest.json>; rel=payment-method-manifest", + kNoResponseBody); EXPECT_CALL(*this, - OnManifestDownload(_, std::string(), + OnManifestDownload(_, kNoContent, + "No content found in payment manifest " + "\"https://bobpay.com/manifest.json\".")); + + ServerResponse(200, Headers::kSend, kNoLinkHeader, kNoResponseBody); +} + +TEST_F(PaymentMethodManifestDownloaderTest, + SecondResponseWithoutHeadersIsFailure) { + ServerResponse(200, Headers::kSend, + "Link: <manifest.json>; rel=payment-method-manifest", + kNoResponseBody); + + EXPECT_CALL(*this, + OnManifestDownload(_, kNoContent, "Unable to download payment manifest " "\"https://bobpay.com/manifest.json\".")); - CallComplete(200, std::string(), std::string(), false); + ServerResponse(200, Headers::kOmit, kNoLinkHeader, kNoResponseBody); +} + +TEST_F(PaymentMethodManifestDownloaderTest, NonEmptySecondResponseIsSuccess) { + ServerResponse(200, Headers::kSend, + "Link: <manifest.json>; rel=payment-method-manifest", + kNoResponseBody); + + EXPECT_CALL(*this, OnManifestDownload(_, "manifest content", kNoError)); + + ServerResponse(200, Headers::kSend, kNoLinkHeader, "manifest content"); } -TEST_F(PaymentMethodManifestDownloaderTest, NonEmptyHttpGetResponseIsSuccess) { - CallComplete(200, "Link: <manifest.json>; rel=payment-method-manifest"); +TEST_F(PaymentMethodManifestDownloaderTest, FirstResponseCode204IsSuccess) { + ServerResponse(204, Headers::kSend, + "Link: <manifest.json>; rel=payment-method-manifest", + kNoResponseBody); EXPECT_CALL(*this, OnManifestDownload(_, "manifest content", kNoError)); - CallComplete(200, std::string(), "manifest content"); + ServerResponse(200, Headers::kSend, kNoLinkHeader, "manifest content"); +} + +TEST_F(PaymentMethodManifestDownloaderTest, SecondResponseCode204IsFailure) { + ServerResponse(204, Headers::kSend, + "Link: <manifest.json>; rel=payment-method-manifest", + kNoResponseBody); + + EXPECT_CALL(*this, + OnManifestDownload(_, kNoContent, + "Unable to download payment manifest " + "\"https://bobpay.com/manifest.json\".")); + + ServerResponse(204, Headers::kSend, kNoLinkHeader, "manifest content"); +} + +TEST_F(PaymentMethodManifestDownloaderTest, + SecondResponseWithLinkHeaderAndNoContentIsFailure) { + ServerResponse(200, Headers::kSend, + "Link: <manifest.json>; rel=payment-method-manifest", + kNoResponseBody); + + EXPECT_CALL(*this, + OnManifestDownload(_, kNoContent, + "No content found in payment manifest " + "\"https://bobpay.com/manifest.json\".")); + + ServerResponse(200, Headers::kSend, + "Link: <manifest.json>; rel=payment-method-manifest", + kNoResponseBody); } -TEST_F(PaymentMethodManifestDownloaderTest, HeaderResponseCode204IsSuccess) { - CallComplete(204, "Link: <manifest.json>; rel=payment-method-manifest"); +TEST_F(PaymentMethodManifestDownloaderTest, + SecondResponseWithLinkHeaderAndWithContentReturnsTheContent) { + ServerResponse(200, Headers::kSend, + "Link: <manifest.json>; rel=payment-method-manifest", + kNoResponseBody); EXPECT_CALL(*this, OnManifestDownload(_, "manifest content", kNoError)); - CallComplete(200, std::string(), "manifest content"); + ServerResponse(200, Headers::kSend, + "Link: <manifest.json>; rel=payment-method-manifest", + "manifest content"); } TEST_F(PaymentMethodManifestDownloaderTest, RelativeHttpHeaderLinkUrl) { - CallComplete(200, "Link: <manifest.json>; rel=payment-method-manifest"); + ServerResponse(200, Headers::kSend, + "Link: <manifest.json>; rel=payment-method-manifest", + kNoResponseBody); EXPECT_EQ("https://bobpay.com/manifest.json", GetOriginalURL()); } TEST_F(PaymentMethodManifestDownloaderTest, AbsoluteHttpsHeaderLinkUrl) { - CallComplete(200, - "Link: <https://bobpay.com/manifest.json>; " - "rel=payment-method-manifest"); + ServerResponse(200, Headers::kSend, + "Link: <https://bobpay.com/manifest.json>; " + "rel=payment-method-manifest", + kNoResponseBody); EXPECT_EQ("https://bobpay.com/manifest.json", GetOriginalURL()); } @@ -217,131 +344,152 @@ TEST_F(PaymentMethodManifestDownloaderTest, AbsoluteHttpHeaderLinkUrl) { EXPECT_CALL( *this, OnManifestDownload( - _, std::string(), + _, kNoContent, "\"http://bobpay.com/manifest.json\" is not a valid payment manifest " "URL with HTTPS scheme (or HTTP scheme for localhost).")); - CallComplete(200, - "Link: <http://bobpay.com/manifest.json>; " - "rel=payment-method-manifest"); + ServerResponse( + 200, Headers::kSend, + "Link: <http://bobpay.com/manifest.json>; rel=payment-method-manifest", + kNoResponseBody); } TEST_F(PaymentMethodManifestDownloaderTest, 300IsUnsupportedRedirect) { EXPECT_CALL(*this, OnManifestDownload( - _, std::string(), + _, kNoContent, "HTTP status code 300 \"Multiple Choices\" not allowed for " "payment method manifest \"https://bobpay.com/\".")); - CallRedirect(300, GURL("https://pay.bobpay.com")); + ServerRedirect(300, GURL("https://pay.bobpay.com")); } TEST_F(PaymentMethodManifestDownloaderTest, 301And302AreSupportedRedirects) { - CallRedirect(301, GURL("https://pay.bobpay.com")); + ServerRedirect(301, GURL("https://pay.bobpay.com")); EXPECT_EQ(GetOriginalURL(), GURL("https://pay.bobpay.com")); - CallRedirect(302, GURL("https://newpay.bobpay.com")); + ServerRedirect(302, GURL("https://newpay.bobpay.com")); EXPECT_EQ(GetOriginalURL(), GURL("https://newpay.bobpay.com")); - CallComplete(200, "Link: <manifest.json>; rel=payment-method-manifest"); + ServerResponse(200, Headers::kSend, + "Link: <manifest.json>; rel=payment-method-manifest", + kNoResponseBody); EXPECT_CALL(*this, OnManifestDownload(_, "manifest content", kNoError)); - CallComplete(200, std::string(), "manifest content"); + ServerResponse(200, Headers::kSend, kNoLinkHeader, "manifest content"); +} + +TEST_F(PaymentMethodManifestDownloaderTest, + CannotRedirectAfterFollowingLinkHeader) { + ServerResponse(200, Headers::kSend, + "Link: <manifest.json>; rel=payment-method-manifest", + kNoResponseBody); + + EXPECT_CALL(*this, OnManifestDownload( + _, kNoContent, + "Unable to download the payment manifest because " + "reached the maximum number of redirects.")); + + ServerRedirect(301, GURL("https://pay.bobpay.com")); } TEST_F(PaymentMethodManifestDownloaderTest, 302And303AreSupportedRedirects) { - CallRedirect(302, GURL("https://pay.bobpay.com")); + ServerRedirect(302, GURL("https://pay.bobpay.com")); EXPECT_EQ(GetOriginalURL(), GURL("https://pay.bobpay.com")); - CallRedirect(303, GURL("https://newpay.bobpay.com")); + ServerRedirect(303, GURL("https://newpay.bobpay.com")); EXPECT_EQ(GetOriginalURL(), GURL("https://newpay.bobpay.com")); - CallComplete(200, "Link: <manifest.json>; rel=payment-method-manifest"); + ServerResponse(200, Headers::kSend, + "Link: <manifest.json>; rel=payment-method-manifest", + kNoResponseBody); EXPECT_CALL(*this, OnManifestDownload(_, "manifest content", kNoError)); - CallComplete(200, std::string(), "manifest content"); + ServerResponse(200, Headers::kSend, kNoLinkHeader, "manifest content"); } TEST_F(PaymentMethodManifestDownloaderTest, 304IsUnsupportedRedirect) { EXPECT_CALL(*this, OnManifestDownload( - _, std::string(), + _, kNoContent, "HTTP status code 304 \"Not Modified\" not allowed for " "payment method manifest \"https://bobpay.com/\".")); - CallRedirect(304, GURL("https://pay.bobpay.com")); + ServerRedirect(304, GURL("https://pay.bobpay.com")); } TEST_F(PaymentMethodManifestDownloaderTest, 305IsUnsupportedRedirect) { EXPECT_CALL(*this, OnManifestDownload( - _, std::string(), + _, kNoContent, "HTTP status code 305 \"Use Proxy\" not allowed for " "payment method manifest \"https://bobpay.com/\".")); - CallRedirect(305, GURL("https://pay.bobpay.com")); + ServerRedirect(305, GURL("https://pay.bobpay.com")); } TEST_F(PaymentMethodManifestDownloaderTest, 307And308AreSupportedRedirects) { - CallRedirect(307, GURL("https://pay.bobpay.com")); + ServerRedirect(307, GURL("https://pay.bobpay.com")); EXPECT_EQ(GetOriginalURL(), GURL("https://pay.bobpay.com")); - CallRedirect(308, GURL("https://newpay.bobpay.com")); + ServerRedirect(308, GURL("https://newpay.bobpay.com")); EXPECT_EQ(GetOriginalURL(), GURL("https://newpay.bobpay.com")); - CallComplete(200, "Link: <manifest.json>; rel=payment-method-manifest"); + ServerResponse(200, Headers::kSend, + "Link: <manifest.json>; rel=payment-method-manifest", + kNoResponseBody); EXPECT_CALL(*this, OnManifestDownload(_, "manifest content", kNoError)); - CallComplete(200, std::string(), "manifest content"); + ServerResponse(200, Headers::kSend, kNoLinkHeader, "manifest content"); } TEST_F(PaymentMethodManifestDownloaderTest, NoMoreThanThreeRedirects) { - CallRedirect(301, GURL("https://pay.bobpay.com")); + ServerRedirect(301, GURL("https://pay.bobpay.com")); EXPECT_EQ(GetOriginalURL(), GURL("https://pay.bobpay.com")); - CallRedirect(302, GURL("https://oldpay.bobpay.com")); + ServerRedirect(302, GURL("https://oldpay.bobpay.com")); EXPECT_EQ(GetOriginalURL(), GURL("https://oldpay.bobpay.com")); - CallRedirect(308, GURL("https://newpay.bobpay.com")); + ServerRedirect(308, GURL("https://newpay.bobpay.com")); EXPECT_EQ(GetOriginalURL(), GURL("https://newpay.bobpay.com")); EXPECT_CALL(*this, OnManifestDownload( - _, std::string(), + _, kNoContent, "Unable to download the payment manifest because " "reached the maximum number of redirects.")); - CallRedirect(308, GURL("https://newpay.bobpay.com")); + ServerRedirect(308, GURL("https://newpay.bobpay.com")); } TEST_F(PaymentMethodManifestDownloaderTest, InvalidRedirectUrlIsFailure) { EXPECT_CALL(*this, OnManifestDownload( - _, std::string(), + _, kNoContent, "\"\" is not a valid payment manifest URL with HTTPS " "scheme (or HTTP scheme for localhost).")); - CallRedirect(308, GURL("pay.bobpay.com")); + ServerRedirect(308, GURL("pay.bobpay.com")); } TEST_F(PaymentMethodManifestDownloaderTest, NotAllowCrossSiteRedirects) { EXPECT_CALL( *this, OnManifestDownload( - _, std::string(), + _, kNoContent, "Cross-site redirect from \"https://bobpay.com/\" to " "\"https://alicepay.com/\" not allowed for payment manifests.")); - CallRedirect(301, GURL("https://alicepay.com")); + ServerRedirect(301, GURL("https://alicepay.com")); } class WebAppManifestDownloaderTest : public testing::Test { @@ -354,7 +502,7 @@ class WebAppManifestDownloaderTest : public testing::Test { downloader_(std::make_unique<ErrorLogger>(), shared_url_loader_factory_) { downloader_.DownloadWebAppManifest( - test_url_, + url::Origin::Create(test_url_), test_url_, base::BindOnce(&WebAppManifestDownloaderTest::OnManifestDownload, base::Unretained(this))); } @@ -366,8 +514,7 @@ class WebAppManifestDownloaderTest : public testing::Test { const std::string& content, const std::string& error_message)); - void CallComplete(int response_code, - const std::string& response_body = std::string()) { + void ServerResponse(int response_code, const std::string& response_body) { scoped_refptr<net::HttpResponseHeaders> headers = base::MakeRefCounted<net::HttpResponseHeaders>(std::string()); headers->ReplaceStatusLine(base::StringPrintf( @@ -393,22 +540,26 @@ TEST_F(WebAppManifestDownloaderTest, HttpGetResponse404IsFailure) { EXPECT_CALL( *this, OnManifestDownload( - _, std::string(), + _, kNoContent, "Unable to download payment manifest \"https://bobpay.com/\".")); - CallComplete(404); + ServerResponse(404, kNoResponseBody); } TEST_F(WebAppManifestDownloaderTest, EmptyHttpGetResponseIsFailure) { - EXPECT_CALL(*this, OnManifestDownload(_, std::string(), kNoError)); + EXPECT_CALL( + *this, + OnManifestDownload( + _, kNoContent, + "No content found in payment manifest \"https://bobpay.com/\".")); - CallComplete(200); + ServerResponse(200, kNoResponseBody); } TEST_F(WebAppManifestDownloaderTest, NonEmptyHttpGetResponseIsSuccess) { EXPECT_CALL(*this, OnManifestDownload(_, "manifest content", kNoError)); - CallComplete(200, "manifest content"); + ServerResponse(200, "manifest content"); } } // namespace payments diff --git a/chromium/components/payments/core/payment_method_data.cc b/chromium/components/payments/core/payment_method_data.cc index 515cc27fb18..0cfb1f9fc7c 100644 --- a/chromium/components/payments/core/payment_method_data.cc +++ b/chromium/components/payments/core/payment_method_data.cc @@ -13,32 +13,10 @@ namespace payments { namespace { // These are defined as part of the spec at: -// https://w3c.github.io/browser-payment-api/#paymentmethoddata-dictionary +// https://w3c.github.io/payment-method-basic-card/ static const char kMethodDataData[] = "data"; static const char kSupportedMethods[] = "supportedMethods"; static const char kSupportedNetworks[] = "supportedNetworks"; -static const char kSupportedTypes[] = "supportedTypes"; - -// Converts |supported_type| to |card_type| and returns true on success. -bool ConvertCardTypeStringToEnum(const std::string& supported_type, - autofill::CreditCard::CardType* card_type) { - if (supported_type == "credit") { - *card_type = autofill::CreditCard::CARD_TYPE_CREDIT; - return true; - } - - if (supported_type == "debit") { - *card_type = autofill::CreditCard::CARD_TYPE_DEBIT; - return true; - } - - if (supported_type == "prepaid") { - *card_type = autofill::CreditCard::CARD_TYPE_PREPAID; - return true; - } - - return false; -} } // namespace @@ -48,8 +26,7 @@ PaymentMethodData::~PaymentMethodData() = default; bool PaymentMethodData::operator==(const PaymentMethodData& other) const { return supported_method == other.supported_method && data == other.data && - supported_networks == other.supported_networks && - supported_types == other.supported_types; + supported_networks == other.supported_networks; } bool PaymentMethodData::operator!=(const PaymentMethodData& other) const { @@ -59,7 +36,6 @@ bool PaymentMethodData::operator!=(const PaymentMethodData& other) const { bool PaymentMethodData::FromDictionaryValue( const base::DictionaryValue& value) { supported_networks.clear(); - supported_types.clear(); // The value of supportedMethods should be a string. if (!value.GetString(kSupportedMethods, &supported_method) || @@ -68,7 +44,7 @@ bool PaymentMethodData::FromDictionaryValue( } // Data is optional, but if a dictionary is present, save a stringified - // version and attempt to parse supportedNetworks/supportedTypes. + // version and attempt to parse supportedNetworks. const base::DictionaryValue* data_dict = nullptr; if (value.GetDictionary(kMethodDataData, &data_dict)) { std::string json_data; @@ -85,20 +61,6 @@ bool PaymentMethodData::FromDictionaryValue( supported_networks.push_back(supported_network); } } - const base::ListValue* supported_types_list = nullptr; - if (data_dict->GetList(kSupportedTypes, &supported_types_list)) { - for (size_t i = 0; i < supported_types_list->GetSize(); ++i) { - std::string supported_type; - if (!supported_types_list->GetString(i, &supported_type) || - !base::IsStringASCII(supported_type)) { - return false; - } - autofill::CreditCard::CardType card_type = - autofill::CreditCard::CARD_TYPE_UNKNOWN; - if (ConvertCardTypeStringToEnum(supported_type, &card_type)) - supported_types.insert(card_type); - } - } } return true; } diff --git a/chromium/components/payments/core/payment_method_data.h b/chromium/components/payments/core/payment_method_data.h index e73b7712d04..5a956248f5f 100644 --- a/chromium/components/payments/core/payment_method_data.h +++ b/chromium/components/payments/core/payment_method_data.h @@ -44,7 +44,6 @@ class PaymentMethodData { // When the methods include "basic-card", a list of networks and types that // are supported. std::vector<std::string> supported_networks; - std::set<autofill::CreditCard::CardType> supported_types; }; } // namespace payments diff --git a/chromium/components/payments/core/payment_method_data_unittest.cc b/chromium/components/payments/core/payment_method_data_unittest.cc index be891a43256..815b7374bac 100644 --- a/chromium/components/payments/core/payment_method_data_unittest.cc +++ b/chromium/components/payments/core/payment_method_data_unittest.cc @@ -14,12 +14,8 @@ namespace payments { TEST(PaymentMethodData, FromDictionaryValueSuccess_SupportedMethodsString) { PaymentMethodData expected; expected.supported_method = "basic-card"; - expected.data = - "{\"supportedNetworks\":[\"mastercard\"]," - "\"supportedTypes\":[\"debit\",\"credit\"]}"; + expected.data = "{\"supportedNetworks\":[\"mastercard\"]}"; expected.supported_networks.push_back("mastercard"); - expected.supported_types.insert(autofill::CreditCard::CARD_TYPE_DEBIT); - expected.supported_types.insert(autofill::CreditCard::CARD_TYPE_CREDIT); base::DictionaryValue method_data_dict; method_data_dict.SetString("supportedMethods", "basic-card"); @@ -27,10 +23,6 @@ TEST(PaymentMethodData, FromDictionaryValueSuccess_SupportedMethodsString) { auto supported_networks_list = std::make_unique<base::ListValue>(); supported_networks_list->AppendString("mastercard"); data_dict->Set("supportedNetworks", std::move(supported_networks_list)); - auto supported_types_list = std::make_unique<base::ListValue>(); - supported_types_list->AppendString("debit"); - supported_types_list->AppendString("credit"); - data_dict->Set("supportedTypes", std::move(supported_types_list)); method_data_dict.Set("data", std::move(data_dict)); PaymentMethodData actual; @@ -100,13 +92,6 @@ TEST(PaymentMethodData, Equality) { EXPECT_NE(method_data1, method_data2); method_data2.supported_networks = supported_networks1; EXPECT_EQ(method_data1, method_data2); - - method_data1.supported_types = {autofill::CreditCard::CARD_TYPE_UNKNOWN}; - EXPECT_NE(method_data1, method_data2); - method_data2.supported_types = {autofill::CreditCard::CARD_TYPE_DEBIT}; - EXPECT_NE(method_data1, method_data2); - method_data2.supported_types = method_data1.supported_types; - EXPECT_EQ(method_data1, method_data2); } } // namespace payments diff --git a/chromium/components/payments/core/payment_request_data_util.cc b/chromium/components/payments/core/payment_request_data_util.cc index f61dc5d8b00..9d7766dc959 100644 --- a/chromium/components/payments/core/payment_request_data_util.cc +++ b/chromium/components/payments/core/payment_request_data_util.cc @@ -146,35 +146,6 @@ void ParseSupportedMethods( } } -void ParseSupportedCardTypes( - const std::vector<PaymentMethodData>& method_data, - std::set<autofill::CreditCard::CardType>* out_supported_card_types_set) { - DCHECK(out_supported_card_types_set->empty()); - - for (const PaymentMethodData& method_data_entry : method_data) { - // Ignore |supported_types| if |supported_method| is not "basic-card". - if (method_data_entry.supported_method != methods::kBasicCard) - continue; - - for (const autofill::CreditCard::CardType& card_type : - method_data_entry.supported_types) { - out_supported_card_types_set->insert(card_type); - } - } - - // Omitting the card types means all 3 card types are supported. - if (out_supported_card_types_set->empty()) { - out_supported_card_types_set->insert( - autofill::CreditCard::CARD_TYPE_CREDIT); - out_supported_card_types_set->insert(autofill::CreditCard::CARD_TYPE_DEBIT); - out_supported_card_types_set->insert( - autofill::CreditCard::CARD_TYPE_PREPAID); - } - - // Let the user decide whether an unknown card type should be used. - out_supported_card_types_set->insert(autofill::CreditCard::CARD_TYPE_UNKNOWN); -} - base::string16 FormatCardNumberForDisplay(const base::string16& card_number) { base::string16 number = autofill::CreditCard::StripSeparators(card_number); if (number.empty() || !base::IsAsciiDigit(number[0])) diff --git a/chromium/components/payments/core/payment_request_data_util.h b/chromium/components/payments/core/payment_request_data_util.h index ae296247d73..633d5173260 100644 --- a/chromium/components/payments/core/payment_request_data_util.h +++ b/chromium/components/payments/core/payment_request_data_util.h @@ -59,14 +59,6 @@ void ParseSupportedMethods( std::vector<GURL>* out_url_payment_method_identifiers, std::set<std::string>* out_payment_method_identifiers); -// Parses the supported card types (e.g., credit, debit, prepaid) from -// supportedTypes. |out_supported_card_types_set| is expected to be empty. It -// will always contain autofill::CreditCard::CARD_TYPE_UNKNOWN after the call. -// Also, it gets filled with all of the card types if supportedTypes is empty. -void ParseSupportedCardTypes( - const std::vector<PaymentMethodData>& method_data, - std::set<autofill::CreditCard::CardType>* out_supported_card_types_set); - // Formats |card_number| for display. For example, "4111111111111111" is // formatted into "4111 1111 1111 1111". This method does not format masked card // numbers, which start with a letter. diff --git a/chromium/components/payments/core/payment_request_data_util_unittest.cc b/chromium/components/payments/core/payment_request_data_util_unittest.cc index 10766484c71..b034fca5f37 100644 --- a/chromium/components/payments/core/payment_request_data_util_unittest.cc +++ b/chromium/components/payments/core/payment_request_data_util_unittest.cc @@ -8,6 +8,7 @@ #include "base/json/json_writer.h" #include "base/macros.h" +#include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/values.h" #include "build/build_config.h" @@ -69,24 +70,26 @@ TEST(PaymentRequestDataUtilTest, GetBasicCardResponseFromAutofillCreditCard) { ->ToDictionaryValue(); std::string json_response; base::JSONWriter::Write(*response_value, &json_response); - EXPECT_EQ( - "{\"billingAddress\":" - "{\"addressLine\":[\"666 Erebus St.\",\"Apt 8\"]," - "\"city\":\"Elysium\"," - "\"country\":\"US\"," - "\"dependentLocality\":\"\"," - "\"organization\":\"Underworld\"," - "\"phone\":\"16502111111\"," - "\"postalCode\":\"91111\"," - "\"recipient\":\"John H. Doe\"," - "\"region\":\"CA\"," - "\"sortingCode\":\"\"}," - "\"cardNumber\":\"4111111111111111\"," - "\"cardSecurityCode\":\"123\"," - "\"cardholderName\":\"Test User\"," - "\"expiryMonth\":\"11\"," - "\"expiryYear\":\"2022\"}", - json_response); + EXPECT_EQ(base::StringPrintf( + "{\"billingAddress\":" + "{\"addressLine\":[\"666 Erebus St.\",\"Apt 8\"]," + "\"city\":\"Elysium\"," + "\"country\":\"US\"," + "\"dependentLocality\":\"\"," + "\"organization\":\"Underworld\"," + "\"phone\":\"16502111111\"," + "\"postalCode\":\"91111\"," + "\"recipient\":\"John H. Doe\"," + "\"region\":\"CA\"," + "\"sortingCode\":\"\"}," + "\"cardNumber\":\"4111111111111111\"," + "\"cardSecurityCode\":\"123\"," + "\"cardholderName\":\"Test User\"," + "\"expiryMonth\":\"%s\"," + "\"expiryYear\":\"%s\"}", + base::UTF16ToUTF8(card.Expiration2DigitMonthAsString()).c_str(), + base::UTF16ToUTF8(card.Expiration4DigitYearAsString()).c_str()), + json_response); } // A test fixture to check ParseSupportedMethods() returns empty supported diff --git a/chromium/components/payments/core/strings_util.cc b/chromium/components/payments/core/strings_util.cc index dab19470d47..1a8b0be84c6 100644 --- a/chromium/components/payments/core/strings_util.cc +++ b/chromium/components/payments/core/strings_util.cc @@ -14,27 +14,6 @@ #include "ui/base/l10n/l10n_util.h" namespace payments { -namespace { - -constexpr size_t kNone = 0; -constexpr size_t kCredit = 1; -constexpr size_t kDebit = 2; -constexpr size_t kPrepaid = 4; - -size_t getCardTypeBitmask( - const std::set<autofill::CreditCard::CardType>& types) { - return (types.find(autofill::CreditCard::CARD_TYPE_CREDIT) != types.end() - ? kCredit - : kNone) | - (types.find(autofill::CreditCard::CARD_TYPE_DEBIT) != types.end() - ? kDebit - : kNone) | - (types.find(autofill::CreditCard::CARD_TYPE_PREPAID) != types.end() - ? kPrepaid - : kNone); -} - -} // namespace base::string16 GetShippingAddressLabelFormAutofillProfile( const autofill::AutofillProfile& profile, @@ -173,47 +152,4 @@ base::string16 GetShippingOptionSectionString( } } -base::string16 GetAcceptedCardTypesText( - const std::set<autofill::CreditCard::CardType>& types) { - int string_ids[8]; - - string_ids[kNone] = IDS_PAYMENTS_ACCEPTED_CARDS_LABEL; - string_ids[kCredit | kDebit | kPrepaid] = IDS_PAYMENTS_ACCEPTED_CARDS_LABEL; - - string_ids[kCredit] = IDS_PAYMENTS_ACCEPTED_CREDIT_CARDS_LABEL; - string_ids[kDebit] = IDS_PAYMENTS_ACCEPTED_DEBIT_CARDS_LABEL; - string_ids[kPrepaid] = IDS_PAYMENTS_ACCEPTED_PREPAID_CARDS_LABEL; - - string_ids[kCredit | kDebit] = IDS_PAYMENTS_ACCEPTED_CREDIT_DEBIT_CARDS_LABEL; - string_ids[kCredit | kPrepaid] = - IDS_PAYMENTS_ACCEPTED_CREDIT_PREPAID_CARDS_LABEL; - string_ids[kDebit | kPrepaid] = - IDS_PAYMENTS_ACCEPTED_DEBIT_PREPAID_CARDS_LABEL; - - return l10n_util::GetStringUTF16(string_ids[getCardTypeBitmask(types)]); -} - -base::string16 GetCardTypesAreAcceptedText( - const std::set<autofill::CreditCard::CardType>& types) { - int string_ids[8]; - - string_ids[kNone] = 0; - string_ids[kCredit | kDebit | kPrepaid] = 0; - - string_ids[kCredit] = IDS_PAYMENTS_CREDIT_CARDS_ARE_ACCEPTED_LABEL; - string_ids[kDebit] = IDS_PAYMENTS_DEBIT_CARDS_ARE_ACCEPTED_LABEL; - string_ids[kPrepaid] = IDS_PAYMENTS_PREPAID_CARDS_ARE_ACCEPTED_LABEL; - - string_ids[kCredit | kDebit] = - IDS_PAYMENTS_CREDIT_DEBIT_CARDS_ARE_ACCEPTED_LABEL; - string_ids[kCredit | kPrepaid] = - IDS_PAYMENTS_CREDIT_PREPAID_CARDS_ARE_ACCEPTED_LABEL; - string_ids[kDebit | kPrepaid] = - IDS_PAYMENTS_DEBIT_PREPAID_CARDS_ARE_ACCEPTED_LABEL; - - int string_id = string_ids[getCardTypeBitmask(types)]; - return string_id == 0 ? base::string16() - : l10n_util::GetStringUTF16(string_id); -} - } // namespace payments diff --git a/chromium/components/payments/core/strings_util.h b/chromium/components/payments/core/strings_util.h index efdf5c364ef..3fcd43a6039 100644 --- a/chromium/components/payments/core/strings_util.h +++ b/chromium/components/payments/core/strings_util.h @@ -61,19 +61,6 @@ base::string16 GetChooseShippingOptionButtonLabel( base::string16 GetShippingOptionSectionString( PaymentShippingType shipping_type); -// Returns the label "Accepted cards" that is customized based on the -// accepted card |types|. For example, "Accepted debit cards". If |types| is -// empty or contains all possible values, then returns the generic "Accepted -// cards" string. -base::string16 GetAcceptedCardTypesText( - const std::set<autofill::CreditCard::CardType>& types); - -// Returns the label "Cards are accepted" that is customized based on the -// accepted card |types|. For example, "Debit cards are accepted". If |types| is -// empty or contains all possible values, then returns an empty string. -base::string16 GetCardTypesAreAcceptedText( - const std::set<autofill::CreditCard::CardType>& types); - } // namespace payments #endif // COMPONENTS_PAYMENTS_CORE_STRINGS_UTIL_H_ diff --git a/chromium/components/payments/core/strings_util_unittest.cc b/chromium/components/payments/core/strings_util_unittest.cc deleted file mode 100644 index e2e82395ac6..00000000000 --- a/chromium/components/payments/core/strings_util_unittest.cc +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "components/payments/core/strings_util.h" - -#include <string> -#include <vector> - -#include "base/stl_util.h" -#include "base/strings/utf_string_conversions.h" -#include "build/build_config.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace payments { -namespace { - -using CardType = ::autofill::CreditCard::CardType; - -constexpr CardType CREDIT = ::autofill::CreditCard::CARD_TYPE_CREDIT; -constexpr CardType DEBIT = ::autofill::CreditCard::CARD_TYPE_DEBIT; -constexpr CardType PREPAID = ::autofill::CreditCard::CARD_TYPE_PREPAID; -constexpr CardType UNKNOWN = ::autofill::CreditCard::CARD_TYPE_UNKNOWN; - -} // namespace - -#if defined(OS_MACOSX) -TEST(StringsUtilTest, GetAcceptedCardTypesText) { - static const struct { - std::vector<CardType> card_types; - const char* const expected_text; - } kTestCases[] = { - {std::vector<CardType>(), "Accepted Cards"}, - {{UNKNOWN}, "Accepted Cards"}, - {{CREDIT}, "Accepted Credit Cards"}, - {{DEBIT}, "Accepted Debit Cards"}, - {{PREPAID}, "Accepted Prepaid Cards"}, - {{CREDIT, DEBIT}, "Accepted Credit and Debit Cards"}, - {{CREDIT, PREPAID}, "Accepted Credit and Prepaid Cards"}, - {{DEBIT, PREPAID}, "Accepted Debit and Prepaid Cards"}, - {{CREDIT, DEBIT, PREPAID}, "Accepted Cards"}, - }; - for (size_t i = 0; i < base::size(kTestCases); ++i) { - EXPECT_EQ( - base::UTF8ToUTF16(kTestCases[i].expected_text), - GetAcceptedCardTypesText(std::set<CardType>( - kTestCases[i].card_types.begin(), kTestCases[i].card_types.end()))); - } -} -#else -TEST(StringsUtilTest, GetAcceptedCardTypesText) { - static const struct { - std::vector<CardType> card_types; - const char* const expected_text; - } kTestCases[] = { - {std::vector<CardType>(), "Accepted cards"}, - {{UNKNOWN}, "Accepted cards"}, - {{CREDIT}, "Accepted credit cards"}, - {{DEBIT}, "Accepted debit cards"}, - {{PREPAID}, "Accepted prepaid cards"}, - {{CREDIT, DEBIT}, "Accepted credit and debit cards"}, - {{CREDIT, PREPAID}, "Accepted credit and prepaid cards"}, - {{DEBIT, PREPAID}, "Accepted debit and prepaid cards"}, - {{CREDIT, DEBIT, PREPAID}, "Accepted cards"}, - }; - for (size_t i = 0; i < base::size(kTestCases); ++i) { - EXPECT_EQ( - base::UTF8ToUTF16(kTestCases[i].expected_text), - GetAcceptedCardTypesText(std::set<CardType>( - kTestCases[i].card_types.begin(), kTestCases[i].card_types.end()))); - } -} -#endif - -TEST(StringsUtilTest, GetCardTypesAreAcceptedText) { - static const struct { - std::vector<CardType> card_types; - const char* const expected_text; - } kTestCases[] = { - {std::vector<CardType>(), ""}, - {{UNKNOWN}, ""}, - {{CREDIT}, "Credit cards are accepted."}, - {{DEBIT}, "Debit cards are accepted."}, - {{PREPAID}, "Prepaid cards are accepted."}, - {{CREDIT, DEBIT}, "Credit and debit cards are accepted."}, - {{CREDIT, PREPAID}, "Credit and prepaid cards are accepted."}, - {{DEBIT, PREPAID}, "Debit and prepaid cards are accepted."}, - {{CREDIT, DEBIT, PREPAID}, ""}, - }; - for (size_t i = 0; i < base::size(kTestCases); ++i) { - EXPECT_EQ( - base::UTF8ToUTF16(kTestCases[i].expected_text), - GetCardTypesAreAcceptedText(std::set<CardType>( - kTestCases[i].card_types.begin(), kTestCases[i].card_types.end()))); - } -} - -} // namespace payments diff --git a/chromium/components/payments/core/test_payment_manifest_downloader.cc b/chromium/components/payments/core/test_payment_manifest_downloader.cc index 319ef305d5c..f1b8b893784 100644 --- a/chromium/components/payments/core/test_payment_manifest_downloader.cc +++ b/chromium/components/payments/core/test_payment_manifest_downloader.cc @@ -27,13 +27,14 @@ void TestDownloader::AddTestServerURL(const std::string& prefix, } void TestDownloader::InitiateDownload( + const url::Origin& request_initiator, const GURL& url, - const std::string& method, + Download::Type download_type, int allowed_number_of_redirects, PaymentManifestDownloadCallback callback) { - PaymentManifestDownloader::InitiateDownload(FindTestServerURL(url), method, - allowed_number_of_redirects, - std::move(callback)); + PaymentManifestDownloader::InitiateDownload( + request_initiator, FindTestServerURL(url), download_type, + allowed_number_of_redirects, std::move(callback)); } GURL TestDownloader::FindTestServerURL(const GURL& url) const { diff --git a/chromium/components/payments/core/test_payment_manifest_downloader.h b/chromium/components/payments/core/test_payment_manifest_downloader.h index 220d7378564..a200a56e256 100644 --- a/chromium/components/payments/core/test_payment_manifest_downloader.h +++ b/chromium/components/payments/core/test_payment_manifest_downloader.h @@ -95,8 +95,9 @@ class TestDownloader : public PaymentManifestDownloader { private: // PaymentManifestDownloader implementation. - void InitiateDownload(const GURL& url, - const std::string& method, + void InitiateDownload(const url::Origin& request_initiator, + const GURL& url, + Download::Type download_type, int allowed_number_of_redirects, PaymentManifestDownloadCallback callback) override; diff --git a/chromium/components/payments/mojom/BUILD.gn b/chromium/components/payments/mojom/BUILD.gn index 6caf4541a79..2b8def3bc43 100644 --- a/chromium/components/payments/mojom/BUILD.gn +++ b/chromium/components/payments/mojom/BUILD.gn @@ -6,9 +6,7 @@ import("//mojo/public/tools/bindings/mojom.gni") mojom("mojom") { generate_java = true - sources = [ - "payment_request_data.mojom", - ] + sources = [ "payment_request_data.mojom" ] # TODO(crbug.com/699569): Convert to use the new JS bindings. # use_new_js_bindings = false |