summaryrefslogtreecommitdiff
path: root/chromium/components/payments/content/android
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/components/payments/content/android')
-rw-r--r--chromium/components/payments/content/android/BUILD.gn45
-rw-r--r--chromium/components/payments/content/android/DEPS1
-rw-r--r--chromium/components/payments/content/android/byte_buffer_helper.h2
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/Address.java19
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/IPaymentDetailsUpdateService.aidl44
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/IPaymentDetailsUpdateServiceCallback.aidl29
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/JniPaymentApp.java260
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/JourneyLogger.java253
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/MojoStructCollection.java30
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/OWNERS3
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/PackageManagerDelegate.java140
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentApp.java67
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentDetailsUpdateService.java76
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentDetailsUpdateServiceHelper.java245
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentFeatureList.java81
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentHandlerHost.java85
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestSpec.java79
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestUpdateEventListener.java68
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/IsReadyToPayServiceHelper.java14
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelper.java131
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelperType.java251
-rw-r--r--chromium/components/payments/content/android/java/src/org/chromium/components/payments/payment_details_update_service.aidl6
-rw-r--r--chromium/components/payments/content/android/java_templates/ErrorStrings.java.tmpl3
-rw-r--r--chromium/components/payments/content/android/jni_payment_app.cc254
-rw-r--r--chromium/components/payments/content/android/jni_payment_app.h101
-rw-r--r--chromium/components/payments/content/android/payment_feature_list.cc69
-rw-r--r--chromium/components/payments/content/android/payment_feature_list.h21
-rw-r--r--chromium/components/payments/content/android/payment_handler_host.cc68
-rw-r--r--chromium/components/payments/content/android/payment_handler_host.h64
-rw-r--r--chromium/components/payments/content/android/payment_request_spec.cc90
-rw-r--r--chromium/components/payments/content/android/payment_request_spec.h66
-rw-r--r--chromium/components/payments/content/android/payment_request_update_event_listener.cc50
-rw-r--r--chromium/components/payments/content/android/payment_request_update_event_listener.h37
33 files changed, 2407 insertions, 345 deletions
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_