summaryrefslogtreecommitdiff
path: root/chromium/components/payments/content/android/java
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2020-10-12 14:27:29 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2020-10-13 09:35:20 +0000
commitc30a6232df03e1efbd9f3b226777b07e087a1122 (patch)
treee992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/components/payments/content/android/java
parent7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff)
downloadqtwebengine-chromium-85-based.tar.gz
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057 Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/components/payments/content/android/java')
-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
19 files changed, 1621 insertions, 260 deletions
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;