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