diff options
Diffstat (limited to 'chromium/components/payments/content/android')
33 files changed, 2407 insertions, 345 deletions
diff --git a/chromium/components/payments/content/android/BUILD.gn b/chromium/components/payments/content/android/BUILD.gn index 72c630f27ec..ba4855549cb 100644 --- a/chromium/components/payments/content/android/BUILD.gn +++ b/chromium/components/payments/content/android/BUILD.gn @@ -13,13 +13,21 @@ static_library("android") { "currency_formatter_android.cc", "currency_formatter_android.h", "error_message_util.cc", + "jni_payment_app.cc", + "jni_payment_app.h", "origin_security_checker_android.cc", + "payment_feature_list.cc", + "payment_feature_list.h", "payment_handler_host.cc", "payment_handler_host.h", "payment_manifest_downloader_android.cc", "payment_manifest_downloader_android.h", "payment_manifest_parser_android.cc", "payment_manifest_parser_android.h", + "payment_request_spec.cc", + "payment_request_spec.h", + "payment_request_update_event_listener.cc", + "payment_request_update_event_listener.h", "payment_validator_android.cc", "url_util.cc", ] @@ -42,10 +50,15 @@ generate_jni("jni_headers") { sources = [ "java/src/org/chromium/components/payments/CurrencyFormatter.java", "java/src/org/chromium/components/payments/ErrorMessageUtil.java", + "java/src/org/chromium/components/payments/JniPaymentApp.java", + "java/src/org/chromium/components/payments/JourneyLogger.java", "java/src/org/chromium/components/payments/OriginSecurityChecker.java", + "java/src/org/chromium/components/payments/PaymentFeatureList.java", "java/src/org/chromium/components/payments/PaymentHandlerHost.java", "java/src/org/chromium/components/payments/PaymentManifestDownloader.java", "java/src/org/chromium/components/payments/PaymentManifestParser.java", + "java/src/org/chromium/components/payments/PaymentRequestSpec.java", + "java/src/org/chromium/components/payments/PaymentRequestUpdateEventListener.java", "java/src/org/chromium/components/payments/PaymentValidator.java", "java/src/org/chromium/components/payments/UrlUtil.java", ] @@ -57,14 +70,23 @@ android_library("java") { "java/src/org/chromium/components/payments/Address.java", "java/src/org/chromium/components/payments/CurrencyFormatter.java", "java/src/org/chromium/components/payments/ErrorMessageUtil.java", + "java/src/org/chromium/components/payments/JniPaymentApp.java", + "java/src/org/chromium/components/payments/JourneyLogger.java", + "java/src/org/chromium/components/payments/MojoStructCollection.java", "java/src/org/chromium/components/payments/OriginSecurityChecker.java", + "java/src/org/chromium/components/payments/PackageManagerDelegate.java", "java/src/org/chromium/components/payments/PayerData.java", "java/src/org/chromium/components/payments/PaymentAddressTypeConverter.java", "java/src/org/chromium/components/payments/PaymentApp.java", "java/src/org/chromium/components/payments/PaymentDetailsConverter.java", + "java/src/org/chromium/components/payments/PaymentDetailsUpdateService.java", + "java/src/org/chromium/components/payments/PaymentDetailsUpdateServiceHelper.java", + "java/src/org/chromium/components/payments/PaymentFeatureList.java", "java/src/org/chromium/components/payments/PaymentHandlerHost.java", "java/src/org/chromium/components/payments/PaymentManifestDownloader.java", "java/src/org/chromium/components/payments/PaymentManifestParser.java", + "java/src/org/chromium/components/payments/PaymentRequestSpec.java", + "java/src/org/chromium/components/payments/PaymentRequestUpdateEventListener.java", "java/src/org/chromium/components/payments/PaymentValidator.java", "java/src/org/chromium/components/payments/UrlUtil.java", "java/src/org/chromium/components/payments/WebAppManifestSection.java", @@ -83,7 +105,20 @@ android_library("java") { "//url:gurl_java", "//url:origin_java", ] - srcjar_deps = [ ":error_strings_generated_srcjar" ] + srcjar_deps = [ + ":error_strings_generated_srcjar", + ":payment_app_type_generated_enum", + ":payment_details_update_service_aidl", + ":payments_journey_logger_enum_javagen", + ] +} + +android_aidl("payment_details_update_service_aidl") { + interface_file = "java/src/org/chromium/components/payments/payment_details_update_service.aidl" + sources = [ + "java/src/org/chromium/components/payments/IPaymentDetailsUpdateService.aidl", + "java/src/org/chromium/components/payments/IPaymentDetailsUpdateServiceCallback.aidl", + ] } java_cpp_strings("error_strings_generated_srcjar") { @@ -97,3 +132,11 @@ java_cpp_strings("method_strings_generated_srcjar") { template = "java_templates/MethodStrings.java.tmpl" } + +java_cpp_enum("payment_app_type_generated_enum") { + sources = [ "//components/payments/content/payment_app.h" ] +} + +java_cpp_enum("payments_journey_logger_enum_javagen") { + sources = [ "//components/payments/core/journey_logger.h" ] +} diff --git a/chromium/components/payments/content/android/DEPS b/chromium/components/payments/content/android/DEPS index c81c188ed20..9edb460c289 100644 --- a/chromium/components/payments/content/android/DEPS +++ b/chromium/components/payments/content/android/DEPS @@ -1,4 +1,5 @@ include_rules = [ "+components/payments/content/android/jni_headers", + "+mojo/public/java", "+services/network/public/cpp", ] diff --git a/chromium/components/payments/content/android/byte_buffer_helper.h b/chromium/components/payments/content/android/byte_buffer_helper.h index fd21d4a2b93..bf1b5d36c4b 100644 --- a/chromium/components/payments/content/android/byte_buffer_helper.h +++ b/chromium/components/payments/content/android/byte_buffer_helper.h @@ -10,7 +10,7 @@ #include <vector> #include "base/android/scoped_java_ref.h" -#include "base/logging.h" +#include "base/check.h" #include "mojo/public/cpp/bindings/struct_ptr.h" namespace payments { diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/Address.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/Address.java index 0d106e4bffe..ee7a4418a3a 100644 --- a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/Address.java +++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/Address.java @@ -8,11 +8,21 @@ import android.os.Bundle; import androidx.annotation.Nullable; +import java.util.regex.Pattern; + /** * An immutable class that mirrors org.chromium.payments.mojom.PaymentAddress. * https://w3c.github.io/payment-request/#paymentaddress-interface */ public class Address { + /** + * The pattern for a valid country code: + * https://w3c.github.io/payment-request/#internal-constructor + */ + private static final String COUNTRY_CODE_PATTERN = "^[A-Z]{2}$"; + @Nullable + private static Pattern sCountryCodePattern; + public final String country; public final String[] addressLine; public final String region; @@ -69,7 +79,7 @@ public class Address { } // Keys in shipping address bundle. - public static final String EXTRA_ADDRESS_COUNTRY = "country"; + public static final String EXTRA_ADDRESS_COUNTRY = "countryCode"; public static final String EXTRA_ADDRESS_LINES = "addressLines"; public static final String EXTRA_ADDRESS_REGION = "region"; public static final String EXTRA_ADDRESS_CITY = "city"; @@ -102,4 +112,11 @@ public class Address { private static String getStringOrEmpty(Bundle bundle, String key) { return bundle.getString(key, /*defaultValue =*/""); } + + public boolean isValid() { + if (sCountryCodePattern == null) { + sCountryCodePattern = Pattern.compile(COUNTRY_CODE_PATTERN); + } + return sCountryCodePattern.matcher(country).matches(); + } } diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/IPaymentDetailsUpdateService.aidl b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/IPaymentDetailsUpdateService.aidl new file mode 100644 index 00000000000..f5db4b4538a --- /dev/null +++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/IPaymentDetailsUpdateService.aidl @@ -0,0 +1,44 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.components.payments; + +import android.os.Bundle; + +import IPaymentDetailsUpdateServiceCallback; + +/** + * Helper interface used by the invoked native payment app to notify the + * browser that the user has selected a different payment method, shipping + * option, or shipping address. + */ +interface IPaymentDetailsUpdateService { + /** + * Called to notify the browser that the user has selected a different + * payment method. + * + * @param paymentHandlerMethodData The data containing the selected payment + * method's name and optional stringified details. + */ + oneway void changePaymentMethod(in Bundle paymentHandlerMethodData, + IPaymentDetailsUpdateServiceCallback callback); + + /** + * Called to notify the browser that the user has selected a different + * shipping option. + * + * @param shippingOptionId The identifier of the selected shipping option. + */ + oneway void changeShippingOption(in String shippingOptionId, + IPaymentDetailsUpdateServiceCallback callback); + + /** + * Called to notify the browser that the user has selected a different + * shipping address. + * + * @param shippingAddress The selected shipping address. + */ + oneway void changeShippingAddress(in Bundle shippingAddress, + IPaymentDetailsUpdateServiceCallback callback); +} diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/IPaymentDetailsUpdateServiceCallback.aidl b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/IPaymentDetailsUpdateServiceCallback.aidl new file mode 100644 index 00000000000..a79898ab20c --- /dev/null +++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/IPaymentDetailsUpdateServiceCallback.aidl @@ -0,0 +1,29 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.components.payments; + +import android.os.Bundle; + +/** + * Helper interface used by the browser to notify the invoked native app about + * merchant's response to one of the paymentmethodchange, shippingoptionchange, + * or shippingaddresschange events. + */ +interface IPaymentDetailsUpdateServiceCallback { + /** + * Called to notify the invoked payment app about updated payment details + * received from the merchant. + * + * @param updatedPaymentDetails The updated payment details received from + * the merchant. + */ + oneway void updateWith(in Bundle updatedPaymentDetails); + + /** + * Called to notify the invoked payment app that the merchant has not + * modified the payment details. + */ + oneway void paymentDetailsNotUpdated(); +} diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/JniPaymentApp.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/JniPaymentApp.java new file mode 100644 index 00000000000..c214d6b1d97 --- /dev/null +++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/JniPaymentApp.java @@ -0,0 +1,260 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.components.payments; + +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.os.Handler; + +import androidx.annotation.Nullable; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.NativeMethods; +import org.chromium.payments.mojom.PaymentDetailsModifier; +import org.chromium.payments.mojom.PaymentItem; +import org.chromium.payments.mojom.PaymentMethodData; +import org.chromium.payments.mojom.PaymentOptions; +import org.chromium.payments.mojom.PaymentRequestDetailsUpdate; +import org.chromium.payments.mojom.PaymentShippingOption; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** Wrapper around a C++ payment app. */ +@JNINamespace("payments") +public class JniPaymentApp extends PaymentApp { + private final Handler mHandler = new Handler(); + private final @PaymentAppType int mPaymentAppType; + + // The Java object owns the C++ payment app and frees it in dismissInstrument(). + private long mNativeObject; + + private AbortCallback mAbortCallback; + private InstrumentDetailsCallback mInvokeCallback; + + @CalledByNative + private JniPaymentApp(String id, String label, String sublabel, Bitmap icon, + @PaymentAppType int paymentAppType, long nativeObject) { + super(id, label, sublabel, new BitmapDrawable(icon)); + mPaymentAppType = paymentAppType; + mNativeObject = nativeObject; + } + + @CalledByNative + public void onAbortResult(boolean aborted) { + mHandler.post(() -> { + if (mAbortCallback == null) return; + mAbortCallback.onInstrumentAbortResult(aborted); + mAbortCallback = null; + }); + } + + @CalledByNative + public void onInvokeResult(String methodName, String stringifiedDetails, PayerData payerData) { + mHandler.post(() -> { + if (mInvokeCallback == null) return; + mInvokeCallback.onInstrumentDetailsReady(methodName, stringifiedDetails, payerData); + mInvokeCallback = null; + }); + } + + @CalledByNative + public void onInvokeError(String errorMessage) { + mHandler.post(() -> { + if (mInvokeCallback == null) return; + mInvokeCallback.onInstrumentDetailsError(errorMessage); + mInvokeCallback = null; + }); + } + + @CalledByNative + private static PayerData createPayerData(String payerName, String payerPhone, String payerEmail, + Address shippingAddress, String selectedShippingOptionId) { + return new PayerData( + payerName, payerPhone, payerEmail, shippingAddress, selectedShippingOptionId); + } + + @CalledByNative + private static Address createShippingAddress(String country, String[] addressLine, + String region, String city, String dependentLocality, String postalCode, + String sortingCode, String organization, String recipient, String phone) { + return new Address(country, addressLine, region, city, dependentLocality, postalCode, + sortingCode, organization, recipient, phone); + } + + @Override + public Set<String> getInstrumentMethodNames() { + return new HashSet<>( + Arrays.asList(JniPaymentAppJni.get().getInstrumentMethodNames(mNativeObject))); + } + + @Override + public boolean isValidForPaymentMethodData(String method, @Nullable PaymentMethodData data) { + return JniPaymentAppJni.get().isValidForPaymentMethodData( + mNativeObject, method, data != null ? data.serialize() : null); + } + + @Override + public boolean handlesShippingAddress() { + return JniPaymentAppJni.get().handlesShippingAddress(mNativeObject); + } + + @Override + public boolean handlesPayerName() { + return JniPaymentAppJni.get().handlesPayerName(mNativeObject); + } + + @Override + public boolean handlesPayerEmail() { + return JniPaymentAppJni.get().handlesPayerEmail(mNativeObject); + } + + @Override + public boolean handlesPayerPhone() { + return JniPaymentAppJni.get().handlesPayerPhone(mNativeObject); + } + + @Override + @Nullable + public String getCountryCode() { + return JniPaymentAppJni.get().getCountryCode(mNativeObject); + } + + @Override + public boolean canMakePayment() { + return JniPaymentAppJni.get().canMakePayment(mNativeObject); + } + + @Override + public boolean canPreselect() { + return JniPaymentAppJni.get().canPreselect(mNativeObject); + } + + @Override + public boolean isUserGestureRequiredToSkipUi() { + return JniPaymentAppJni.get().isUserGestureRequiredToSkipUi(mNativeObject); + } + + @Override + public void invokePaymentApp(String id, String merchantName, String origin, String iframeOrigin, + @Nullable byte[][] certificateChain, Map<String, PaymentMethodData> methodDataMap, + PaymentItem total, List<PaymentItem> displayItems, + Map<String, PaymentDetailsModifier> modifiers, PaymentOptions paymentOptions, + List<PaymentShippingOption> shippingOptions, InstrumentDetailsCallback callback) { + mInvokeCallback = callback; + JniPaymentAppJni.get().invokePaymentApp(mNativeObject, /*callback=*/this); + } + + @Override + public void updateWith(PaymentRequestDetailsUpdate response) { + JniPaymentAppJni.get().updateWith(mNativeObject, response.serialize()); + } + + @Override + public void onPaymentDetailsNotUpdated() { + JniPaymentAppJni.get().onPaymentDetailsNotUpdated(mNativeObject); + } + + @Override + public boolean isWaitingForPaymentDetailsUpdate() { + return JniPaymentAppJni.get().isWaitingForPaymentDetailsUpdate(mNativeObject); + } + + @Override + public void abortPaymentApp(AbortCallback callback) { + mAbortCallback = callback; + JniPaymentAppJni.get().abortPaymentApp(mNativeObject, this); + } + + @Override + public boolean isReadyForMinimalUI() { + return JniPaymentAppJni.get().isReadyForMinimalUI(mNativeObject); + } + + @Override + @Nullable + public String accountBalance() { + return JniPaymentAppJni.get().accountBalance(mNativeObject); + } + + @Override + public void disableShowingOwnUI() { + JniPaymentAppJni.get().disableShowingOwnUI(mNativeObject); + } + + @Override + @Nullable + public String getApplicationIdentifierToHide() { + return JniPaymentAppJni.get().getApplicationIdentifierToHide(mNativeObject); + } + + @Override + @Nullable + public Set<String> getApplicationIdentifiersThatHideThisApp() { + return new HashSet<>(Arrays.asList( + JniPaymentAppJni.get().getApplicationIdentifiersThatHideThisApp(mNativeObject))); + } + + @Override + public long getUkmSourceId() { + return JniPaymentAppJni.get().getUkmSourceId(mNativeObject); + } + + @Override + public void setPaymentHandlerHost(PaymentHandlerHost host) { + JniPaymentAppJni.get().setPaymentHandlerHost(mNativeObject, host); + } + + @Override + public void dismissInstrument() { + if (mNativeObject == 0) return; + JniPaymentAppJni.get().freeNativeObject(mNativeObject); + mNativeObject = 0; + } + + @Override + public void finalize() throws Throwable { + dismissInstrument(); + super.finalize(); + } + + @Override + public @PaymentAppType int getPaymentAppType() { + return mPaymentAppType; + } + + @NativeMethods + interface Natives { + String[] getInstrumentMethodNames(long nativeJniPaymentApp); + boolean isValidForPaymentMethodData( + long nativeJniPaymentApp, String method, ByteBuffer dataByteBuffer); + boolean handlesShippingAddress(long nativeJniPaymentApp); + boolean handlesPayerName(long nativeJniPaymentApp); + boolean handlesPayerEmail(long nativeJniPaymentApp); + boolean handlesPayerPhone(long nativeJniPaymentApp); + String getCountryCode(long nativeJniPaymentApp); + boolean canMakePayment(long nativeJniPaymentApp); + boolean canPreselect(long nativeJniPaymentApp); + boolean isUserGestureRequiredToSkipUi(long nativeJniPaymentApp); + void invokePaymentApp(long nativeJniPaymentApp, JniPaymentApp callback); + void updateWith(long nativeJniPaymentApp, ByteBuffer responseByteBuffer); + void onPaymentDetailsNotUpdated(long nativeJniPaymentApp); + boolean isWaitingForPaymentDetailsUpdate(long nativeJniPaymentApp); + void abortPaymentApp(long nativeJniPaymentApp, JniPaymentApp callback); + boolean isReadyForMinimalUI(long nativeJniPaymentApp); + String accountBalance(long nativeJniPaymentApp); + void disableShowingOwnUI(long nativeJniPaymentApp); + String getApplicationIdentifierToHide(long nativeJniPaymentApp); + String[] getApplicationIdentifiersThatHideThisApp(long nativeJniPaymentApp); + long getUkmSourceId(long nativeJniPaymentApp); + void setPaymentHandlerHost(long nativeJniPaymentApp, PaymentHandlerHost paymentHandlerHost); + void freeNativeObject(long nativeJniPaymentApp); + } +} diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/JourneyLogger.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/JourneyLogger.java new file mode 100644 index 00000000000..93a29b3b056 --- /dev/null +++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/JourneyLogger.java @@ -0,0 +1,253 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.components.payments; + +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.NativeMethods; +import org.chromium.content_public.browser.WebContents; + +/** + * A class used to record journey metrics for the Payment Request feature. + */ +@JNINamespace("payments") +public class JourneyLogger { + /** + * Pointer to the native implementation. + */ + private long mJourneyLoggerAndroid; + + private boolean mHasRecorded; + + public JourneyLogger(boolean isIncognito, WebContents webContents) { + // Note that this pointer could leak the native object. The called must call destroy() to + // ensure that the native object is destroyed. + mJourneyLoggerAndroid = JourneyLoggerJni.get().initJourneyLoggerAndroid( + JourneyLogger.this, isIncognito, webContents); + } + + /** Will destroy the native object. This class shouldn't be used afterwards. */ + public void destroy() { + if (mJourneyLoggerAndroid != 0) { + JourneyLoggerJni.get().destroy(mJourneyLoggerAndroid, JourneyLogger.this); + mJourneyLoggerAndroid = 0; + } + } + + /** + * Sets the number of suggestions shown for the specified section. + * + * @param section The section for which to log. + * @param number The number of suggestions. + * @param hasCompleteSuggestion Whether the section has at least one + * complete suggestion. + */ + public void setNumberOfSuggestionsShown( + int section, int number, boolean hasCompleteSuggestion) { + assert section < Section.MAX; + JourneyLoggerJni.get().setNumberOfSuggestionsShown( + mJourneyLoggerAndroid, JourneyLogger.this, section, number, hasCompleteSuggestion); + } + + /** + * Increments the number of selection changes for the specified section. + * + * @param section The section for which to log. + */ + public void incrementSelectionChanges(int section) { + assert section < Section.MAX; + JourneyLoggerJni.get().incrementSelectionChanges( + mJourneyLoggerAndroid, JourneyLogger.this, section); + } + + /** + * Increments the number of selection edits for the specified section. + * + * @param section The section for which to log. + */ + public void incrementSelectionEdits(int section) { + assert section < Section.MAX; + JourneyLoggerJni.get().incrementSelectionEdits( + mJourneyLoggerAndroid, JourneyLogger.this, section); + } + + /** + * Increments the number of selection adds for the specified section. + * + * @param section The section for which to log. + */ + public void incrementSelectionAdds(int section) { + assert section < Section.MAX; + JourneyLoggerJni.get().incrementSelectionAdds( + mJourneyLoggerAndroid, JourneyLogger.this, section); + } + + /** + * Records the fact that the merchant called CanMakePayment and records its return value. + * + * @param value The return value of the CanMakePayment call. + */ + public void setCanMakePaymentValue(boolean value) { + JourneyLoggerJni.get().setCanMakePaymentValue( + mJourneyLoggerAndroid, JourneyLogger.this, value); + } + + /** + * Records the fact that the merchant called HasEnrolledInstrument and records its return value. + * + * @param value The return value of the HasEnrolledInstrument call. + */ + public void setHasEnrolledInstrumentValue(boolean value) { + JourneyLoggerJni.get().setHasEnrolledInstrumentValue( + mJourneyLoggerAndroid, JourneyLogger.this, value); + } + + /** + * Records that an event occurred. + * + * @param event The event that occurred. + */ + public void setEventOccurred(int event) { + assert event >= 0; + assert event < Event.ENUM_MAX; + + JourneyLoggerJni.get().setEventOccurred(mJourneyLoggerAndroid, JourneyLogger.this, event); + } + + /* + * Records what user information were requested by the merchant to complete the Payment Request. + * + * @param requestShipping Whether the merchant requested a shipping address. + * @param requestEmail Whether the merchant requested an email address. + * @param requestPhone Whether the merchant requested a phone number. + * @param requestName Whether the merchant requestes a name. + */ + public void setRequestedInformation(boolean requestShipping, boolean requestEmail, + boolean requestPhone, boolean requestName) { + JourneyLoggerJni.get().setRequestedInformation(mJourneyLoggerAndroid, JourneyLogger.this, + requestShipping, requestEmail, requestPhone, requestName); + } + + /* + * Records what types of payment methods were requested by the merchant in the Payment Request. + * + * @param requestedBasicCard Whether the merchant requested basic-card. + * @param requestedMethodGoogle Whether the merchant requested a Google payment method. + * @param requestedMethodOther Whether the merchant requested a non basic-card, non-Google + * payment method. + */ + public void setRequestedPaymentMethodTypes(boolean requestedBasicCard, + boolean requestedMethodGoogle, boolean requestedMethodOther) { + JourneyLoggerJni.get().setRequestedPaymentMethodTypes(mJourneyLoggerAndroid, + JourneyLogger.this, requestedBasicCard, requestedMethodGoogle, + requestedMethodOther); + } + + /** + * Records that the Payment Request was completed sucessfully. Also starts the logging of + * all the journey logger metrics. + */ + public void setCompleted() { + assert !mHasRecorded; + + if (!mHasRecorded) { + mHasRecorded = true; + JourneyLoggerJni.get().setCompleted(mJourneyLoggerAndroid, JourneyLogger.this); + } + } + + /** + * Records that the Payment Request was aborted and for what reason. Also starts the logging of + * all the journey logger metrics. + * + * @param reason An int indicating why the payment request was aborted. + */ + public void setAborted(int reason) { + assert reason < AbortReason.MAX; + + // The abort reasons on Android cascade into each other, so only the first one should be + // recorded. + if (!mHasRecorded) { + mHasRecorded = true; + JourneyLoggerJni.get().setAborted(mJourneyLoggerAndroid, JourneyLogger.this, reason); + } + } + + /** + * Records that the Payment Request was not shown to the user and for what reason. + * + * @param reason An int indicating why the payment request was not shown. + */ + public void setNotShown(int reason) { + assert reason < NotShownReason.MAX; + assert !mHasRecorded; + + if (!mHasRecorded) { + mHasRecorded = true; + JourneyLoggerJni.get().setNotShown(mJourneyLoggerAndroid, JourneyLogger.this, reason); + } + } + + /** + * Records amount of completed/triggered transactions separated by currency. + * + * @param curreny A string indicating the curreny of the transaction. + * @param value A string indicating the value of the transaction. + * @param completed A boolean indicating whether the transaction has completed or not. + */ + public void recordTransactionAmount(String currency, String value, boolean completed) { + JourneyLoggerJni.get().recordTransactionAmount( + mJourneyLoggerAndroid, JourneyLogger.this, currency, value, completed); + } + + /** + * Records the time when request.show() is called. + */ + public void setTriggerTime() { + JourneyLoggerJni.get().setTriggerTime(mJourneyLoggerAndroid, JourneyLogger.this); + } + + /** + * Sets the ukm source id of payment app. + * @param sourceId A long indicating the ukm source id of the invoked payment app. + */ + public void setPaymentAppUkmSourceId(long sourceId) { + JourneyLoggerJni.get().setPaymentAppUkmSourceId( + mJourneyLoggerAndroid, JourneyLogger.this, sourceId); + } + + @NativeMethods + interface Natives { + long initJourneyLoggerAndroid( + JourneyLogger caller, boolean isIncognito, WebContents webContents); + void destroy(long nativeJourneyLoggerAndroid, JourneyLogger caller); + void setNumberOfSuggestionsShown(long nativeJourneyLoggerAndroid, JourneyLogger caller, + int section, int number, boolean hasCompleteSuggestion); + void incrementSelectionChanges( + long nativeJourneyLoggerAndroid, JourneyLogger caller, int section); + void incrementSelectionEdits( + long nativeJourneyLoggerAndroid, JourneyLogger caller, int section); + void incrementSelectionAdds( + long nativeJourneyLoggerAndroid, JourneyLogger caller, int section); + void setCanMakePaymentValue( + long nativeJourneyLoggerAndroid, JourneyLogger caller, boolean value); + void setHasEnrolledInstrumentValue( + long nativeJourneyLoggerAndroid, JourneyLogger caller, boolean value); + void setEventOccurred(long nativeJourneyLoggerAndroid, JourneyLogger caller, int event); + void setRequestedInformation(long nativeJourneyLoggerAndroid, JourneyLogger caller, + boolean requestShipping, boolean requestEmail, boolean requestPhone, + boolean requestName); + void setRequestedPaymentMethodTypes(long nativeJourneyLoggerAndroid, JourneyLogger caller, + boolean requestedBasicCard, boolean requestedMethodGoogle, + boolean requestedMethodOther); + void setCompleted(long nativeJourneyLoggerAndroid, JourneyLogger caller); + void setAborted(long nativeJourneyLoggerAndroid, JourneyLogger caller, int reason); + void setNotShown(long nativeJourneyLoggerAndroid, JourneyLogger caller, int reason); + void recordTransactionAmount(long nativeJourneyLoggerAndroid, JourneyLogger caller, + String currency, String value, boolean completed); + void setTriggerTime(long nativeJourneyLoggerAndroid, JourneyLogger caller); + void setPaymentAppUkmSourceId( + long nativeJourneyLoggerAndroid, JourneyLogger caller, long sourceId); + } +} diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/MojoStructCollection.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/MojoStructCollection.java new file mode 100644 index 00000000000..1aabcb611a7 --- /dev/null +++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/MojoStructCollection.java @@ -0,0 +1,30 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.components.payments; + +import org.chromium.mojo.bindings.Struct; + +import java.nio.ByteBuffer; +import java.util.Collection; + +/** Helper class for serializing a collection of Mojo structs. */ +public class MojoStructCollection { + /** + * Serialize a collection of Mojo structs. + * @param collection A collection of Mojo structs to serialize. + * @return An array of Mojo structs serialized into byte buffer objects. + */ + public static <T extends Struct> ByteBuffer[] serialize(Collection<T> collection) { + ByteBuffer[] result = new ByteBuffer[collection.size()]; + int i = 0; + for (T item : collection) { + result[i++] = item.serialize(); + } + return result; + } + + // Prevent instantiation. + private MojoStructCollection() {} +} diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/OWNERS b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/OWNERS index 971a6e658aa..db4625d4570 100644 --- a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/OWNERS +++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/OWNERS @@ -5,3 +5,6 @@ file://components/payments/OWNERS per-file *TypeConverter*.*=set noparent per-file *TypeConverter*.*=file://ipc/SECURITY_OWNERS + +per-file *.aidl=set noparent +per-file *.aidl=file://ipc/SECURITY_OWNERS diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PackageManagerDelegate.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PackageManagerDelegate.java new file mode 100644 index 00000000000..b84634c4da2 --- /dev/null +++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PackageManagerDelegate.java @@ -0,0 +1,140 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.components.payments; + +import android.annotation.SuppressLint; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.os.StrictMode; +import android.os.StrictMode.ThreadPolicy; + +import androidx.annotation.Nullable; + +import org.chromium.base.ContextUtils; +import org.chromium.base.PackageManagerUtils; + +import java.util.List; + +/** Abstraction of Android's package manager to enable testing. */ +public class PackageManagerDelegate { + /** + * Checks whether the system has the given feature. + * @param feature The feature to check. + * @return Whether the system has the given feature. + */ + public boolean hasSystemFeature(String feature) { + return ContextUtils.getApplicationContext().getPackageManager().hasSystemFeature(feature); + } + + /** + * Retrieves package information of an installed application. + * + * @param packageName The package name of an installed application. + * @return The package information of the installed application. + */ + @SuppressLint("PackageManagerGetSignatures") + public PackageInfo getPackageInfoWithSignatures(String packageName) { + try { + return ContextUtils.getApplicationContext().getPackageManager().getPackageInfo( + packageName, PackageManager.GET_SIGNATURES); + } catch (NameNotFoundException e) { + return null; + } + } + + /** + * Retrieves package information of an installed application. + * + * @param uid The uid of an installed application. + * @return The package information of the installed application. + */ + @SuppressLint("PackageManagerGetSignatures") + public PackageInfo getPackageInfoWithSignatures(int uid) { + String packageName = + ContextUtils.getApplicationContext().getPackageManager().getNameForUid(uid); + if (packageName == null) return null; + return getPackageInfoWithSignatures(packageName); + } + + /** + * Retrieves the list of activities that can respond to the given intent. + * @param intent The intent to query. + * @return The list of activities that can respond to the intent. + */ + public List<ResolveInfo> getActivitiesThatCanRespondToIntent(Intent intent) { + return PackageManagerUtils.queryIntentActivities(intent, 0); + } + + /** + * Retrieves the list of activities that can respond to the given intent. And returns the + * activites' meta data in ResolveInfo. + * + * @param intent The intent to query. + * @return The list of activities that can respond to the intent. + */ + public List<ResolveInfo> getActivitiesThatCanRespondToIntentWithMetaData(Intent intent) { + return PackageManagerUtils.queryIntentActivities(intent, PackageManager.GET_META_DATA); + } + + /** + * Retrieves the list of services that can respond to the given intent. + * @param intent The intent to query. + * @return The list of services that can respond to the intent. + */ + public List<ResolveInfo> getServicesThatCanRespondToIntent(Intent intent) { + ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); + try { + return ContextUtils.getApplicationContext().getPackageManager().queryIntentServices( + intent, 0); + } finally { + StrictMode.setThreadPolicy(oldPolicy); + } + } + + /** + * Retrieves the label of the app. + * @param resolveInfo The identifying information for an app. + * @return The label for this app. + */ + public CharSequence getAppLabel(ResolveInfo resolveInfo) { + return resolveInfo.loadLabel(ContextUtils.getApplicationContext().getPackageManager()); + } + + /** + * Retrieves the icon of the app. + * @param resolveInfo The identifying information for an app. + * @return The icon for this app. + */ + public Drawable getAppIcon(ResolveInfo resolveInfo) { + return resolveInfo.loadIcon(ContextUtils.getApplicationContext().getPackageManager()); + } + + /** + * Gets the string array resource of the given application. + * + * @param applicationInfo The application info. + * @param resourceId The identifier of the string array resource. + * @return The string array resource, or null if not found. + */ + @Nullable + public String[] getStringArrayResourceForApplication( + ApplicationInfo applicationInfo, int resourceId) { + Resources resources; + try { + resources = ContextUtils.getApplicationContext() + .getPackageManager() + .getResourcesForApplication(applicationInfo); + } catch (NameNotFoundException e) { + return null; + } + return resources == null ? null : resources.getStringArray(resourceId); + } +} diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentApp.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentApp.java index 7f8d259b441..b719873aa8c 100644 --- a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentApp.java +++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentApp.java @@ -11,7 +11,6 @@ import androidx.annotation.Nullable; import org.chromium.base.task.PostTask; import org.chromium.components.autofill.EditableOption; import org.chromium.content_public.browser.UiThreadTaskTraits; -import org.chromium.payments.mojom.PaymentAddress; import org.chromium.payments.mojom.PaymentDetailsModifier; import org.chromium.payments.mojom.PaymentItem; import org.chromium.payments.mojom.PaymentMethodData; @@ -36,47 +35,6 @@ public abstract class PaymentApp extends EditableOption { protected boolean mHaveRequestedAutofillData; /** - * The interface for listener to payment method, shipping address, and shipping option change - * events. Note: What the spec calls "payment methods" in the context of a "change event", this - * code calls "apps". - */ - public interface PaymentRequestUpdateEventListener { - /** - * Called to notify merchant of payment method change. The payment app should block user - * interaction until updateWith() or onPaymentDetailsNotUpdated(). - * https://w3c.github.io/payment-request/#paymentmethodchangeevent-interface - * - * @param methodName Method name. For example, "https://google.com/pay". Should not - * be null or empty. - * @param stringifiedDetails JSON-serialized object. For example, {"type": "debit"}. Should - * not be null. - * @return Whether the payment state was valid. - */ - boolean changePaymentMethodFromInvokedApp(String methodName, String stringifiedDetails); - - /** - * Called to notify merchant of shipping option change. The payment app should block user - * interaction until updateWith() or onPaymentDetailsNotUpdated(). - * https://w3c.github.io/payment-request/#dom-paymentrequestupdateevent - * - * @param shippingOptionId Selected shipping option Identifier, Should not be null or - * empty. - * @return Whether the payment state wa valid. - */ - boolean changeShippingOptionFromInvokedApp(String shippingOptionId); - - /** - * Called to notify merchant of shipping address change. The payment app should block user - * interaction until updateWith() or onPaymentDetailsNotUpdated(). - * https://w3c.github.io/payment-request/#dom-paymentrequestupdateevent - * - * @param shippingAddress Selected shipping address. Should not be null. - * @return Whether the payment state wa valid. - */ - boolean changeShippingAddressFromInvokedApp(PaymentAddress shippingAddress); - } - - /** * The interface for the requester of payment details from the app. */ public interface InstrumentDetailsCallback { @@ -284,11 +242,9 @@ public abstract class PaymentApp extends EditableOption { /** * Abort invocation of the payment app. - * - * @param id The unique identifier of the PaymentRequest. * @param callback The callback to return abort result. */ - public void abortPaymentApp(String id, AbortCallback callback) { + public void abortPaymentApp(AbortCallback callback) { PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() { @Override public void run() { @@ -300,20 +256,11 @@ public abstract class PaymentApp extends EditableOption { /** Cleans up any resources held by the payment app. For example, closes server connections. */ public abstract void dismissInstrument(); - /** @param readyForMnimalUI Whether the payment app is ready for minimal UI flow. */ - public void setIsReadyForMinimalUI(boolean isReadyForMinimalUI) {} - /** @return Whether the payment app is ready for a minimal UI flow. */ public boolean isReadyForMinimalUI() { return false; } - /** - * @param accountBalance The account balance of the payment handler that is ready for a minimal - * UI flow. - */ - public void setAccountBalance(@Nullable String accountBalance) {} - /** @return Account balance for minimal UI flow. */ @Nullable public String accountBalance() { @@ -347,4 +294,16 @@ public abstract class PaymentApp extends EditableOption { public long getUkmSourceId() { return 0; } + + /** + * Sets the endpoint for payment handler communication. Must be called before invoking this + * payment app. Used only by payment apps that are backed by a payment handler. + * @param host The endpoint for payment handler communication. Should not be null. + */ + public void setPaymentHandlerHost(PaymentHandlerHost host) {} + + /** @return The type of payment app. */ + public @PaymentAppType int getPaymentAppType() { + return PaymentAppType.UNDEFINED; + } } diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentDetailsUpdateService.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentDetailsUpdateService.java new file mode 100644 index 00000000000..c5e92262e2b --- /dev/null +++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentDetailsUpdateService.java @@ -0,0 +1,76 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.components.payments; + +import android.app.Service; +import android.content.Intent; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; + +import org.chromium.base.task.PostTask; +import org.chromium.content_public.browser.UiThreadTaskTraits; + +/** + * A bound service responsible for receiving change payment method, shipping option, and shipping + * address calls from an inoked native payment app. + */ +public class PaymentDetailsUpdateService extends Service { + // AIDL calls can happen on multiple threads in parallel. The binder uses PostTask for + // synchronization since locks are discouraged in Chromium. The UI thread task runner is used + // rather than a SequencedTaskRunner since the state of the helper class is also changed by + // PaymentRequestImpl.java, which runs on the UI thread. + private final IPaymentDetailsUpdateService.Stub mBinder = + new IPaymentDetailsUpdateService.Stub() { + @Override + public void changePaymentMethod(Bundle paymentHandlerMethodData, + IPaymentDetailsUpdateServiceCallback callback) { + int callingUid = Binder.getCallingUid(); + PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, () -> { + if (!PaymentDetailsUpdateServiceHelper.getInstance().isCallerAuthorized( + callingUid)) { + return; + } + PaymentDetailsUpdateServiceHelper.getInstance().changePaymentMethod( + paymentHandlerMethodData, callback); + }); + } + @Override + public void changeShippingOption( + String shippingOptionId, IPaymentDetailsUpdateServiceCallback callback) { + int callingUid = Binder.getCallingUid(); + PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, () -> { + if (!PaymentDetailsUpdateServiceHelper.getInstance().isCallerAuthorized( + callingUid)) { + return; + } + PaymentDetailsUpdateServiceHelper.getInstance().changeShippingOption( + shippingOptionId, callback); + }); + } + @Override + public void changeShippingAddress( + Bundle shippingAddress, IPaymentDetailsUpdateServiceCallback callback) { + int callingUid = Binder.getCallingUid(); + PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, () -> { + if (!PaymentDetailsUpdateServiceHelper.getInstance().isCallerAuthorized( + callingUid)) { + return; + } + PaymentDetailsUpdateServiceHelper.getInstance().changeShippingAddress( + shippingAddress, callback); + }); + } + }; + + @Override + public IBinder onBind(Intent intent) { + if (!PaymentFeatureList.isEnabledOrExperimentalFeaturesEnabled( + PaymentFeatureList.ANDROID_APP_PAYMENT_UPDATE_EVENTS)) { + return null; + } + return mBinder; + } +} diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentDetailsUpdateServiceHelper.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentDetailsUpdateServiceHelper.java new file mode 100644 index 00000000000..9ba256556c4 --- /dev/null +++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentDetailsUpdateServiceHelper.java @@ -0,0 +1,245 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.components.payments; + +import android.content.pm.PackageInfo; +import android.content.pm.Signature; +import android.os.Bundle; +import android.os.RemoteException; +import android.text.TextUtils; + +import androidx.annotation.Nullable; + +import org.chromium.base.Log; +import org.chromium.base.ThreadUtils; +import org.chromium.components.payments.intent.WebPaymentIntentHelperType.PaymentHandlerMethodData; +import org.chromium.components.payments.intent.WebPaymentIntentHelperType.PaymentRequestDetailsUpdate; + +import java.util.Arrays; + +/** + * Helper class used by android payment app to notify the browser that the user has selected a + * different payment instrument, shipping option, or shipping address inside native app. + */ +public class PaymentDetailsUpdateServiceHelper { + private static final String TAG = "PaymentDetailsUpdate"; + + @Nullable + private IPaymentDetailsUpdateServiceCallback mCallback; + @Nullable + private PaymentRequestUpdateEventListener mListener; + @Nullable + private PackageInfo mInvokedAppPackageInfo; + @Nullable + private PackageManagerDelegate mPackageManagerDelegate; + + // Singleton instance. + private static PaymentDetailsUpdateServiceHelper sInstance; + private PaymentDetailsUpdateServiceHelper(){}; + + /** + * Returns the singleton instance, lazily creating one if needed. + * The instance is only useful after its listener is set which happens when a native android app + * gets invoked. + * @return The singleton instance. + */ + public static PaymentDetailsUpdateServiceHelper getInstance() { + ThreadUtils.assertOnUiThread(); + if (sInstance == null) sInstance = new PaymentDetailsUpdateServiceHelper(); + return sInstance; + } + + /** + * Initializes the service helper, called when an AndroidPaymentApp is invoked. + * @param packageManagerDelegate The package manager used used to authorize the connecting app. + * @param invokedAppPackageName The package name of the invoked payment app, used to authorize + * the connecting app. + * @param listener The listener for payment method, shipping address, and shipping option + * changes. + */ + public void initialize(PackageManagerDelegate packageManagerDelegate, + String invokedAppPackageName, PaymentRequestUpdateEventListener listener) { + ThreadUtils.assertOnUiThread(); + assert mListener == null; + mListener = listener; + mPackageManagerDelegate = packageManagerDelegate; + mInvokedAppPackageInfo = + mPackageManagerDelegate.getPackageInfoWithSignatures(invokedAppPackageName); + } + + /** + * Called to notify the merchant that the user has selected a different payment method. + * @param paymentHandlerMethodData The data containing the selected payment method's name and + * optional stringified details. + * @param callback The callback used to notify the invoked app about updated payment details. + */ + public void changePaymentMethod( + Bundle paymentHandlerMethodData, IPaymentDetailsUpdateServiceCallback callback) { + ThreadUtils.assertOnUiThread(); + if (paymentHandlerMethodData == null) { + runCallbackWithError(ErrorStrings.METHOD_DATA_REQUIRED, callback); + return; + } + String methodName = + paymentHandlerMethodData.getString(PaymentHandlerMethodData.EXTRA_METHOD_NAME); + if (TextUtils.isEmpty(methodName)) { + runCallbackWithError(ErrorStrings.METHOD_NAME_REQUIRED, callback); + return; + } + + String stringifiedDetails = paymentHandlerMethodData.getString( + PaymentHandlerMethodData.EXTRA_STRINGIFIED_DETAILS, /*defaultValue=*/"{}"); + if (isWaitingForPaymentDetailsUpdate() || mListener == null + || !mListener.changePaymentMethodFromInvokedApp(methodName, stringifiedDetails)) { + runCallbackWithError(ErrorStrings.INVALID_STATE, callback); + return; + } + mCallback = callback; + } + + /** + * Called to notify the merchant that the user has selected a different shipping option. + * @param shippingOptionId The identifier of the selected shipping option. + * @param callback The callback used to notify the invoked app about updated payment details. + */ + public void changeShippingOption( + String shippingOptionId, IPaymentDetailsUpdateServiceCallback callback) { + ThreadUtils.assertOnUiThread(); + if (TextUtils.isEmpty(shippingOptionId)) { + runCallbackWithError(ErrorStrings.SHIPPING_OPTION_ID_REQUIRED, callback); + return; + } + + if (isWaitingForPaymentDetailsUpdate() || mListener == null + || !mListener.changeShippingOptionFromInvokedApp(shippingOptionId)) { + runCallbackWithError(ErrorStrings.INVALID_STATE, callback); + return; + } + mCallback = callback; + } + + /** + * Called to notify the merchant that the user has selected a different shipping address. + * @param shippingAddress The selected shipping address + * @param callback The callback used to notify the invoked app about updated payment details. + */ + public void changeShippingAddress( + Bundle shippingAddress, IPaymentDetailsUpdateServiceCallback callback) { + ThreadUtils.assertOnUiThread(); + if (shippingAddress == null || shippingAddress.isEmpty()) { + runCallbackWithError(ErrorStrings.SHIPPING_ADDRESS_INVALID, callback); + return; + } + + Address address = Address.createFromBundle(shippingAddress); + if (!address.isValid()) { + runCallbackWithError(ErrorStrings.SHIPPING_ADDRESS_INVALID, callback); + return; + } + + if (isWaitingForPaymentDetailsUpdate() || mListener == null + || !mListener.changeShippingAddressFromInvokedApp( + PaymentAddressTypeConverter.convertAddressToMojoPaymentAddress(address))) { + runCallbackWithError(ErrorStrings.INVALID_STATE, callback); + return; + } + mCallback = callback; + } + + /** + * Resets the singleton instance. + */ + public void reset() { + ThreadUtils.assertOnUiThread(); + sInstance = null; + } + + /** + * Checks whether any payment method, shipping address or shipping option change event is + * ongoing. + * @return True after invoked payment app has bound PaymentDetaialsUpdateService and called + * changePaymentMethod, changeShippingAddress, or changeShippingOption and before the + * merchant replies with either updateWith() or onPaymentDetailsNotUpdated(). + */ + public boolean isWaitingForPaymentDetailsUpdate() { + ThreadUtils.assertOnUiThread(); + return mCallback != null; + } + + /** + * Notifies the invoked app about merchant's response to the change event. + * @param response - Modified payment request details to be sent to the invoked app. + */ + public void updateWith(PaymentRequestDetailsUpdate response) { + ThreadUtils.assertOnUiThread(); + if (mCallback == null) return; + try { + mCallback.updateWith(response.asBundle()); + } catch (RemoteException e) { + Log.e(TAG, "Error calling updateWith", e); + } finally { + mCallback = null; + } + } + + /** + * Notfies the invoked app that the merchant has not updated any of the payment request details + * in response to a change event. + */ + public void onPaymentDetailsNotUpdated() { + ThreadUtils.assertOnUiThread(); + if (mCallback == null) return; + try { + mCallback.paymentDetailsNotUpdated(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling paymentDetailsNotUpdated", e); + } finally { + mCallback = null; + } + } + + /** + * @param callerUid The Uid of the service requester. + * @return True when the service requester's package name and signature are the same as the + * invoked payment app's. + */ + public boolean isCallerAuthorized(int callerUid) { + ThreadUtils.assertOnUiThread(); + if (mPackageManagerDelegate == null) { + Log.e(TAG, ErrorStrings.UNATHORIZED_SERVICE_REQUEST); + return false; + } + PackageInfo callerPackageInfo = + mPackageManagerDelegate.getPackageInfoWithSignatures(callerUid); + if (mInvokedAppPackageInfo == null || callerPackageInfo == null + || !mInvokedAppPackageInfo.packageName.equals(callerPackageInfo.packageName)) { + Log.e(TAG, ErrorStrings.UNATHORIZED_SERVICE_REQUEST); + return false; + } + + // TODO(https://crbug.com/1086485): signatures field is deprecated in API level 28. + Signature[] callerSignatures = callerPackageInfo.signatures; + Signature[] invokedAppSignatures = mInvokedAppPackageInfo.signatures; + + boolean result = Arrays.equals(callerSignatures, invokedAppSignatures); + if (!result) Log.e(TAG, ErrorStrings.UNATHORIZED_SERVICE_REQUEST); + return result; + } + + private void runCallbackWithError( + String errorMessage, IPaymentDetailsUpdateServiceCallback callback) { + ThreadUtils.assertOnUiThread(); + if (callback == null) return; + // Only populate the error field. + Bundle blankUpdatedPaymentDetails = new Bundle(); + blankUpdatedPaymentDetails.putString( + PaymentRequestDetailsUpdate.EXTRA_ERROR_MESSAGE, errorMessage); + try { + callback.updateWith(blankUpdatedPaymentDetails); + } catch (RemoteException e) { + Log.e(TAG, "Error calling updateWith", e); + } + } +} diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentFeatureList.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentFeatureList.java new file mode 100644 index 00000000000..28bf46d2499 --- /dev/null +++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentFeatureList.java @@ -0,0 +1,81 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.components.payments; + +import org.chromium.base.FeatureList; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.NativeMethods; + +/** + * Exposes payment specific features in to java since files in org.chromium.components.payments + * package package cannot depend on + * org.chromium.chrome.browser.flags.org.chromium.chrome.browser.flags.ChromeFeatureList. + */ +@JNINamespace("payments::android") +public class PaymentFeatureList { + /** Alphabetical: */ + public static final String ANDROID_APP_PAYMENT_UPDATE_EVENTS = "AndroidAppPaymentUpdateEvents"; + public static final String PAYMENT_REQUEST_SKIP_TO_GPAY = "PaymentRequestSkipToGPay"; + public static final String PAYMENT_REQUEST_SKIP_TO_GPAY_IF_NO_CARD = + "PaymentRequestSkipToGPayIfNoCard"; + public static final String SCROLL_TO_EXPAND_PAYMENT_HANDLER = "ScrollToExpandPaymentHandler"; + public static final String SERVICE_WORKER_PAYMENT_APPS = "ServiceWorkerPaymentApps"; + public static final String STRICT_HAS_ENROLLED_AUTOFILL_INSTRUMENT = + "StrictHasEnrolledAutofillInstrument"; + public static final String WEB_PAYMENTS = "WebPayments"; + public static final String WEB_PAYMENTS_ALWAYS_ALLOW_JUST_IN_TIME_PAYMENT_APP = + "AlwaysAllowJustInTimePaymentApp"; + public static final String WEB_PAYMENTS_APP_STORE_BILLING_DEBUG = "AppStoreBillingDebug"; + public static final String WEB_PAYMENTS_EXPERIMENTAL_FEATURES = + "WebPaymentsExperimentalFeatures"; + public static final String WEB_PAYMENTS_METHOD_SECTION_ORDER_V2 = + "WebPaymentsMethodSectionOrderV2"; + public static final String WEB_PAYMENTS_MINIMAL_UI = "WebPaymentsMinimalUI"; + public static final String WEB_PAYMENTS_MODIFIERS = "WebPaymentsModifiers"; + public static final String WEB_PAYMENTS_REDACT_SHIPPING_ADDRESS = + "WebPaymentsRedactShippingAddress"; + public static final String WEB_PAYMENTS_RETURN_GOOGLE_PAY_IN_BASIC_CARD = + "ReturnGooglePayInBasicCard"; + public static final String WEB_PAYMENTS_SINGLE_APP_UI_SKIP = "WebPaymentsSingleAppUiSkip"; + + // Do not instantiate this class. + private PaymentFeatureList() {} + + /** + * Returns whether the specified feature is enabled or not. + * + * Note: Features queried through this API must be added to the array + * |kFeaturesExposedToJava| in components/payments/content/android/payment_feature_list.cc + * + * @param featureName The name of the feature to query. + * @return Whether the feature is enabled or not. + */ + public static boolean isEnabled(String featureName) { + assert FeatureList.isNativeInitialized(); + return PaymentFeatureListJni.get().isEnabled(featureName); + } + + /** + * Returns whether the feature is enabled or not. * + * Note: Features queried through this API must be added to the array + * |kFeaturesExposedToJava| in components/payments/content/android/payment_feature_list.cc + * + * @param featureName The name of the feature to query. + * @return true when either the specified feature or |WEB_PAYMENTS_EXPERIMENTAL_FEATURES| is + * enabled. + */ + public static boolean isEnabledOrExperimentalFeaturesEnabled(String featureName) { + return isEnabled(WEB_PAYMENTS_EXPERIMENTAL_FEATURES) || isEnabled(featureName); + } + + /** + * The interface implemented by the automatically generated JNI bindings class + * PaymentsFeatureListJni. + */ + @NativeMethods + /* package */ interface Natives { + boolean isEnabled(String featureName); + } +} diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentHandlerHost.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentHandlerHost.java index 1f4d531c966..393c131bdf8 100644 --- a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentHandlerHost.java +++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentHandlerHost.java @@ -8,7 +8,6 @@ import org.chromium.base.annotations.CalledByNative; import org.chromium.base.annotations.JNINamespace; import org.chromium.base.annotations.NativeMethods; import org.chromium.content_public.browser.WebContents; -import org.chromium.payments.mojom.PaymentAddress; import org.chromium.payments.mojom.PaymentRequestDetailsUpdate; import java.nio.ByteBuffer; @@ -19,42 +18,6 @@ import java.nio.ByteBuffer; */ @JNINamespace("payments::android") public class PaymentHandlerHost { - /** - * The interface to be implemented by the object that can communicate to the merchant renderer - * process. - */ - public interface PaymentHandlerHostDelegate { - /** - * Notifies the merchant that the payment method has changed within a payment handler. The - * merchant may recalculate the total based on the changed billing address, for example. - * @param methodName The payment method identifier. - * @param stringifiedData The stringified method-specific data. - * @return "False" if not in a valid state. - */ - @CalledByNative("PaymentHandlerHostDelegate") - boolean changePaymentMethodFromPaymentHandler(String methodName, String stringifiedData); - - /** - * Notifies the merchant that the selected shipping option has changed within a payment - * handler. The merchant may recalculate the payment details (e.g. total) based on the - * updated shipping option. - * @param shippingOptionId The selected shipping option identifier. - * @return "False" if not in a valid state. - */ - @CalledByNative("PaymentHandlerHostDelegate") - boolean changeShippingOptionFromPaymentHandler(String shippingOptionId); - - /** - * Notifies the merchant that the selected shipping address has changed within a payment - * handler. The merchant may recalculate the payment details (e.g. total or shipping - * options) based on the updated shipping address. - * @param shippingAddress The selected shipping address. - * @return "False" if not in a valid state. - */ - @CalledByNative("PaymentHandlerHostDelegate") - boolean changeShippingAddressFromPaymentHandler(PaymentAddress shippingAddress); - } - /** Pointer to the native bridge. This Java object owns the native bridge. */ private long mNativePointer; @@ -63,10 +26,10 @@ public class PaymentHandlerHost { * bridge. The caller must call destroy() when finished using this Java object. * @param webContents The web contents in the same browser context as the payment handler. Used * for logging in developer tools. - * @param delegate The object that can communicate to the merchant renderer process. + * @param listener The object that can communicate to the merchant renderer process. */ - public PaymentHandlerHost(WebContents webContents, PaymentHandlerHostDelegate delegate) { - mNativePointer = PaymentHandlerHostJni.get().init(webContents, delegate); + public PaymentHandlerHost(WebContents webContents, PaymentRequestUpdateEventListener listener) { + mNativePointer = PaymentHandlerHostJni.get().init(webContents, listener); } /** @@ -81,13 +44,13 @@ public class PaymentHandlerHost { } /** - * Returns the pointer to the native payment handler host object. The native bridge owns this - * object. - * @return The pointer to the native payments::PaymentHandlerHost (not the native bridge - * payments::android::PaymentHandlerHost). + * Returns the pointer to the native bridge. The Java object owns this bridge. + * @return The pointer to the native bridge payments::android::PaymentHandlerHost (not the + * cross-platform payment handler host payments::PaymentHandlerHost). */ - public long getNativePaymentHandlerHost() { - return PaymentHandlerHostJni.get().getNativePaymentHandlerHost(mNativePointer); + @CalledByNative + public long getNativeBridge() { + return mNativePointer; } /** @@ -114,24 +77,6 @@ public class PaymentHandlerHost { mNativePointer = 0; } - @CalledByNative - private static Object createShippingAddress(String country, String[] addressLine, String region, - String city, String dependentLocality, String postalCode, String sortingCode, - String organization, String recipient, String phone) { - PaymentAddress result = new PaymentAddress(); - result.country = country; - result.addressLine = addressLine; - result.region = region; - result.city = city; - result.dependentLocality = dependentLocality; - result.postalCode = postalCode; - result.sortingCode = sortingCode; - result.organization = organization; - result.recipient = recipient; - result.phone = phone; - return result; - } - /** * The interface implemented by the automatically generated JNI bindings class * PaymentHandlerHostJni. @@ -143,10 +88,10 @@ public class PaymentHandlerHost { * call destroy(nativePaymentHandlerHost) when done. * @param webContents The web contents in the same browser context as the payment handler. * Used for logging in developer tools. - * @param delegate The object that can communicate to the merchant renderer process. + * @param listener The object that can communicate to the merchant renderer process. * @return The pointer to the native payment handler host bridge. */ - long init(WebContents webContents, PaymentHandlerHostDelegate delegate); + long init(WebContents webContents, PaymentRequestUpdateEventListener listener); /** * Checks whether any payment method, shipping address, or shipping option change is @@ -156,14 +101,6 @@ public class PaymentHandlerHost { boolean isWaitingForPaymentDetailsUpdate(long nativePaymentHandlerHost); /** - * Returns the native pointer to the payment handler host (not the bridge). The native - * bridge owns the returned pointer. - * @param nativePaymentHandlerHost The pointer to the native payment handler host bridge. - * @return The pointer to the native payment handler host. - */ - long getNativePaymentHandlerHost(long nativePaymentHandlerHost); - - /** * Notifies the payment handler that the merchant has updated the payment details. * @param nativePaymentHandlerHost The pointer to the native payment handler host bridge. * @param responseBuffer The serialized payment method change response from the merchant. diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestSpec.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestSpec.java new file mode 100644 index 00000000000..8d1616f1e58 --- /dev/null +++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestSpec.java @@ -0,0 +1,79 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.components.payments; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.NativeMethods; +import org.chromium.payments.mojom.PaymentDetails; +import org.chromium.payments.mojom.PaymentMethodData; +import org.chromium.payments.mojom.PaymentOptions; +import org.chromium.payments.mojom.PaymentValidationErrors; + +import java.nio.ByteBuffer; +import java.util.Collection; + +/** + * Container for information received from the renderer that invoked the Payment Request API. Owns + * an instance of native payment_request_spec.cc, so the destroy() method has to be called to free + * the native pointer. + */ +@JNINamespace("payments::android") +public class PaymentRequestSpec { + private long mNativePointer; + + /** + * Stores the information received from the renderer that invoked the Payment Request API. + * Creates an instance of native payment_request_spec.cc with the given parameters. + * @param options The payment options, e.g., whether shipping is requested. + * @param details The payment details, e.g., the total amount. + * @param methodData The list of supported payment method identifiers and corresponding payment + * method specific data. + * @param appLocale The current application locale. + */ + public PaymentRequestSpec(PaymentOptions options, PaymentDetails details, + Collection<PaymentMethodData> methodData, String appLocale) { + mNativePointer = PaymentRequestSpecJni.get().create(options.serialize(), + details.serialize(), MojoStructCollection.serialize(methodData), appLocale); + } + + /** + * Called when the renderer updates the payment details in response to, e.g., new shipping + * address. + * @param details The updated payment details, e.g., the updated total amount. + */ + public void updateWith(PaymentDetails details) { + PaymentRequestSpecJni.get().updateWith(mNativePointer, details.serialize()); + } + + /** + * Called when merchant retries a failed payment. + * @param validationErrors The information about the fields that failed the validation. + */ + public void retry(PaymentValidationErrors validationErrors) { + PaymentRequestSpecJni.get().retry(mNativePointer, validationErrors.serialize()); + } + + /** Destroys the native pointer. */ + public void destroy() { + if (mNativePointer == 0) return; + PaymentRequestSpecJni.get().destroy(mNativePointer); + mNativePointer = 0; + } + + @CalledByNative + private long getNativePointer() { + return mNativePointer; + } + + @NativeMethods + /* package */ interface Natives { + long create(ByteBuffer optionsByteBuffer, ByteBuffer detailsByteBuffer, + ByteBuffer[] methodDataByteBuffers, String appLocale); + void updateWith(long nativePaymentRequestSpec, ByteBuffer detailsByteBuffer); + void retry(long nativePaymentRequestSpec, ByteBuffer validationErrorsByteBuffer); + void destroy(long nativePaymentRequestSpec); + } +} diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestUpdateEventListener.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestUpdateEventListener.java new file mode 100644 index 00000000000..0df1ec56672 --- /dev/null +++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestUpdateEventListener.java @@ -0,0 +1,68 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.components.payments; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.payments.mojom.PaymentAddress; + +import java.nio.ByteBuffer; + +/** + * The interface for listener to payment method, shipping address, and shipping option change + * events. Note: What the spec calls "payment methods" in the context of a "change event", this + * code calls "apps". + */ +@JNINamespace("payments::android") +public interface PaymentRequestUpdateEventListener { + /** + * Called to notify merchant of payment method change. The payment app should block user + * interaction until updateWith() or onPaymentDetailsNotUpdated(). + * https://w3c.github.io/payment-request/#paymentmethodchangeevent-interface + * + * @param methodName Method name. For example, "https://google.com/pay". Should not + * be null or empty. + * @param stringifiedDetails JSON-serialized object. For example, {"type": "debit"}. Should + * not be null. + * @return Whether the payment state was valid. + */ + @CalledByNative + boolean changePaymentMethodFromInvokedApp(String methodName, String stringifiedDetails); + + /** + * Called to notify merchant of shipping option change. The payment app should block user + * interaction until updateWith() or onPaymentDetailsNotUpdated(). + * https://w3c.github.io/payment-request/#dom-paymentrequestupdateevent + * + * @param shippingOptionId Selected shipping option Identifier, Should not be null or + * empty. + * @return Whether the payment state was valid. + */ + @CalledByNative + boolean changeShippingOptionFromInvokedApp(String shippingOptionId); + + /** + * Called to notify merchant of shipping address change. The payment app should block user + * interaction until updateWith() or onPaymentDetailsNotUpdated(). + * https://w3c.github.io/payment-request/#dom-paymentrequestupdateevent + * + * @param shippingAddress Selected shipping address. Should not be null. + * @return Whether the payment state was valid. + */ + boolean changeShippingAddressFromInvokedApp(PaymentAddress shippingAddress); + + /** + * Called to notify merchant of shipping address change. The payment app should block user + * interaction until updateWith() or onPaymentDetailsNotUpdated(). + * https://w3c.github.io/payment-request/#dom-paymentrequestupdateevent + * + * @param shippingAddress Selected shipping address in serialized form. Should not be null. + * @return Whether the payment state was valid. + */ + @CalledByNative + default boolean changeShippingAddress(ByteBuffer shippingAddress) { + return changeShippingAddressFromInvokedApp(PaymentAddress.deserialize(shippingAddress)); + } +} diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/IsReadyToPayServiceHelper.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/IsReadyToPayServiceHelper.java index 8d8be4aee39..b7428536a24 100644 --- a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/IsReadyToPayServiceHelper.java +++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/IsReadyToPayServiceHelper.java @@ -31,6 +31,7 @@ public class IsReadyToPayServiceHelper private boolean mIsServiceBindingInitiated; private boolean mIsReadyToPayQueried; private Handler mHandler; + private Intent mIsReadyToPayIntent; /** The callback that returns the result (success or error) to the helper's caller. */ public interface ResultHandler { @@ -45,8 +46,7 @@ public class IsReadyToPayServiceHelper } /** - * The constructor starts the IsReadyToPay service. The result would be returned asynchronously - * with one callback. + * Initiate the helper. * @param context The application context. Should not be null. * @param isReadyToPayIntent The IsReaddyToPay intent created by {@link * WebPaymentIntentHelper#createIsReadyToPayIntent}. Should not be null. @@ -60,6 +60,14 @@ public class IsReadyToPayServiceHelper mContext = context; mResultHandler = resultHandler; mHandler = new Handler(); + mIsReadyToPayIntent = isReadyToPayIntent; + } + + /** + * Query the IsReadyToPay service. The result would be returned in the resultHandler callback + * asynchronously. Note that resultHandler would be invoked only once. + */ + public void query() { try { // This method returns "true if the system is in the process of bringing up a // service that your client has permission to bind to; false if the system couldn't @@ -68,7 +76,7 @@ public class IsReadyToPayServiceHelper // the connection." // https://developer.android.com/reference/android/content/Context.html#bindService(android.content.Intent,%20android.content.ServiceConnection,%20int) mIsServiceBindingInitiated = mContext.bindService( - isReadyToPayIntent, /*serviceConnection=*/this, Context.BIND_AUTO_CREATE); + mIsReadyToPayIntent, /*serviceConnection=*/this, Context.BIND_AUTO_CREATE); } catch (SecurityException e) { // Intentionally blank, so mIsServiceBindingInitiated is false. } diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelper.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelper.java index b13bd0173b5..b035c031668 100644 --- a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelper.java +++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelper.java @@ -16,7 +16,6 @@ import androidx.annotation.Nullable; import org.chromium.components.payments.Address; import org.chromium.components.payments.ErrorStrings; import org.chromium.components.payments.PayerData; -import org.chromium.components.payments.intent.WebPaymentIntentHelperType.PaymentCurrencyAmount; import org.chromium.components.payments.intent.WebPaymentIntentHelperType.PaymentDetailsModifier; import org.chromium.components.payments.intent.WebPaymentIntentHelperType.PaymentItem; import org.chromium.components.payments.intent.WebPaymentIntentHelperType.PaymentMethodData; @@ -26,7 +25,6 @@ import org.chromium.components.payments.intent.WebPaymentIntentHelperType.Paymen import java.io.IOException; import java.io.StringWriter; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.Map; @@ -50,12 +48,12 @@ public class WebPaymentIntentHelper { public static final String EXTRA_TOP_ORIGIN = "topLevelOrigin"; public static final String EXTRA_TOTAL = "total"; public static final String EXTRA_PAYMENT_OPTIONS = "paymentOptions"; + public static final String EXTRA_PAYMENT_OPTIONS_REQUEST_PAYER_NAME = "requestPayerName"; + public static final String EXTRA_PAYMENT_OPTIONS_REQUEST_PAYER_PHONE = "requestPayerPhone"; + public static final String EXTRA_PAYMENT_OPTIONS_REQUEST_PAYER_EMAIL = "requestPayerEmail"; + public static final String EXTRA_PAYMENT_OPTIONS_REQUEST_SHIPPING = "requestShipping"; + public static final String EXTRA_PAYMENT_OPTIONS_SHIPPING_TYPE = "shippingType"; public static final String EXTRA_SHIPPING_OPTIONS = "shippingOptions"; - public static final String EXTRA_SHIPPING_OPTION_ID = "shippingOptionId"; - public static final String EXTRA_SHIPPING_OPTION_LABEL = "label"; - public static final String EXTRA_SHIPPING_OPTION_SELECTED = "selected"; - public static final String EXTRA_SHIPPING_OPTION_AMOUNT_CURRENCY = "amountCurrency"; - public static final String EXTRA_SHIPPING_OPTION_AMOUNT_VALUE = "amountValue"; // Deprecated parameters sent to the payment app for backward compatibility. public static final String EXTRA_DEPRECATED_CERTIFICATE_CHAIN = "certificateChain"; @@ -74,6 +72,7 @@ public class WebPaymentIntentHelper { public static final String EXTRA_RESPONSE_PAYER_NAME = "payerName"; public static final String EXTRA_RESPONSE_PAYER_EMAIL = "payerEmail"; public static final String EXTRA_RESPONSE_PAYER_PHONE = "payerPhone"; + public static final String EXTRA_SHIPPING_OPTION_ID = "shippingOptionId"; // Shipping address bundle used in payment response and shippingAddressChange. public static final String EXTRA_SHIPPING_ADDRESS = "shippingAddress"; @@ -371,22 +370,24 @@ public class WebPaymentIntentHelper { extras.putParcelable(EXTRA_METHOD_DATA, methodDataBundle); if (modifiers != null) { - extras.putString(EXTRA_MODIFIERS, serializeModifiers(modifiers.values())); + extras.putString( + EXTRA_MODIFIERS, PaymentDetailsModifier.serializeModifiers(modifiers.values())); } if (total != null) { - String serializedTotalAmount = serializeTotalAmount(total.amount); + String serializedTotalAmount = total.amount.serialize(); extras.putString(EXTRA_TOTAL, serializedTotalAmount == null ? EMPTY_JSON_DATA : serializedTotalAmount); } if (paymentOptions != null) { - extras.putStringArrayList(EXTRA_PAYMENT_OPTIONS, paymentOptions.asStringArrayList()); + extras.putBundle(EXTRA_PAYMENT_OPTIONS, buildPaymentOptionsBundle(paymentOptions)); } // ShippingOptions are populated only when shipping is requested. if (paymentOptions != null && paymentOptions.requestShipping) { - Parcelable[] serializedShippingOptionList = buildShippingOptionList(shippingOptions); + Parcelable[] serializedShippingOptionList = + PaymentShippingOption.buildPaymentShippingOptionList(shippingOptions); extras.putParcelableArray(EXTRA_SHIPPING_OPTIONS, serializedShippingOptionList); } @@ -436,20 +437,19 @@ public class WebPaymentIntentHelper { return result; } - private static Parcelable[] buildShippingOptionList( - List<PaymentShippingOption> shippingOptions) { - Parcelable[] result = new Parcelable[shippingOptions.size()]; - int index = 0; - for (PaymentShippingOption option : shippingOptions) { - Bundle bundle = new Bundle(); - bundle.putString(EXTRA_SHIPPING_OPTION_ID, option.id); - bundle.putString(EXTRA_SHIPPING_OPTION_LABEL, option.label); - bundle.putString(EXTRA_SHIPPING_OPTION_AMOUNT_CURRENCY, option.amountCurrency); - bundle.putString(EXTRA_SHIPPING_OPTION_AMOUNT_VALUE, option.amountValue); - bundle.putBoolean(EXTRA_SHIPPING_OPTION_SELECTED, option.selected); - result[index++] = bundle; + private static Bundle buildPaymentOptionsBundle(PaymentOptions paymentOptions) { + Bundle bundle = new Bundle(); + bundle.putBoolean( + EXTRA_PAYMENT_OPTIONS_REQUEST_PAYER_NAME, paymentOptions.requestPayerName); + bundle.putBoolean( + EXTRA_PAYMENT_OPTIONS_REQUEST_PAYER_EMAIL, paymentOptions.requestPayerEmail); + bundle.putBoolean( + EXTRA_PAYMENT_OPTIONS_REQUEST_PAYER_PHONE, paymentOptions.requestPayerPhone); + bundle.putBoolean(EXTRA_PAYMENT_OPTIONS_REQUEST_SHIPPING, paymentOptions.requestShipping); + if (paymentOptions.shippingType != null) { + bundle.putString(EXTRA_PAYMENT_OPTIONS_SHIPPING_TYPE, paymentOptions.shippingType); } - return result; + return bundle; } private static String deprecatedSerializeDetails( @@ -463,7 +463,7 @@ public class WebPaymentIntentHelper { if (total != null) { // total {{{ json.name("total"); - serializeTotal(total, json); + total.serializeAndRedact(json); // }}} total } @@ -484,87 +484,6 @@ public class WebPaymentIntentHelper { return stringWriter.toString(); } - private static String serializeTotalAmount(PaymentCurrencyAmount totalAmount) { - StringWriter stringWriter = new StringWriter(); - JsonWriter json = new JsonWriter(stringWriter); - try { - // {{{ - json.beginObject(); - json.name("currency").value(totalAmount.currency); - json.name("value").value(totalAmount.value); - json.endObject(); - // }}} - } catch (IOException e) { - return null; - } - return stringWriter.toString(); - } - - private static void serializeTotal(PaymentItem item, JsonWriter json) throws IOException { - // item {{{ - json.beginObject(); - // Sanitize the total name, because the payment app does not need it to complete the - // transaction. Matches the behavior of: - // https://w3c.github.io/payment-handler/#total-attribute - json.name("label").value(""); - - // amount {{{ - json.name("amount").beginObject(); - json.name("currency").value(item.amount.currency); - json.name("value").value(item.amount.value); - json.endObject(); - // }}} amount - - json.endObject(); - // }}} item - } - - private static String serializeModifiers(Collection<PaymentDetailsModifier> modifiers) { - StringWriter stringWriter = new StringWriter(); - JsonWriter json = new JsonWriter(stringWriter); - try { - json.beginArray(); - for (PaymentDetailsModifier modifier : modifiers) { - checkNotNull(modifier, "PaymentDetailsModifier"); - serializeModifier(modifier, json); - } - json.endArray(); - } catch (IOException e) { - return EMPTY_JSON_DATA; - } - return stringWriter.toString(); - } - - private static void serializeModifier(PaymentDetailsModifier modifier, JsonWriter json) - throws IOException { - // {{{ - json.beginObject(); - - // total {{{ - if (modifier.total != null) { - json.name("total"); - serializeTotal(modifier.total, json); - } else { - json.name("total").nullValue(); - } - // }}} total - - // TODO(https://crbug.com/754779): The supportedMethods field was already changed from array - // to string but we should keep backward-compatibility for now. - // supportedMethods {{{ - json.name("supportedMethods").beginArray(); - json.value(modifier.methodData.supportedMethod); - json.endArray(); - // }}} supportedMethods - - // data {{{ - json.name("data").value(modifier.methodData.stringifiedData); - // }}} - - json.endObject(); - // }}} - } - private static String getStringOrEmpty(Intent data, String key) { return data.getExtras().getString(key, /*defaultValue =*/""); } diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelperType.java b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelperType.java index 14e17efde93..8ac1a1d876d 100644 --- a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelperType.java +++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/intent/WebPaymentIntentHelperType.java @@ -4,33 +4,79 @@ package org.chromium.components.payments.intent; -import androidx.annotation.Nullable; +import android.os.Bundle; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.JsonWriter; -import java.util.ArrayList; +import androidx.annotation.Nullable; +import java.io.IOException; +import java.io.StringWriter; +import java.util.Collection; +import java.util.List; /** * The types that corresponds to the types in org.chromium.payments.mojom. The fields of these types * are the subset of those in the mojom types. The subset is minimally selected based on the need of * this package. This class should be independent of the org.chromium package. * * @see <a - * href="https://developers.google.com/web/fundamentals/payments/payment-apps-developer-guide/android-payment-apps#payment_parameters">Payment + * href="https://web.dev/android-payment-apps-overview/#parameters-2">Payment * parameters</a> * @see <a - * href="https://developers.google.com/web/fundamentals/payments/payment-apps-developer-guide/android-payment-apps#%E2%80%9Cis_ready_to_pay%E2%80%9D_parameters">“Is + * href="https://web.dev/android-payment-apps-overview/#parameters">“Is * ready to pay” parameters</a> */ public final class WebPaymentIntentHelperType { + private static final String EMPTY_JSON_DATA = "{}"; + /** * The class that corresponds to mojom.PaymentCurrencyAmount, with minimally required fields. */ public static final class PaymentCurrencyAmount { + public static String EXTRA_CURRENCY = "currency"; + public static String EXTRA_VALUE = "value"; + public final String currency; public final String value; public PaymentCurrencyAmount(String currency, String value) { this.currency = currency; this.value = value; } + + /** + * Serializes this object into the provided json writer. + * @param json The json object to which the seri + */ + public void serialize(JsonWriter json) throws IOException { + // {{{ + json.beginObject(); + json.name("currency").value(currency); + json.name("value").value(value); + json.endObject(); + // }}} + } + /** + * Serializes this object + * @return The serialized payment currency amount. + */ + public String serialize() { + StringWriter stringWriter = new StringWriter(); + JsonWriter json = new JsonWriter(stringWriter); + try { + serialize(json); + } catch (IOException e) { + return null; + } + return stringWriter.toString(); + } + + /* package */ Bundle asBundle() { + Bundle bundle = new Bundle(); + bundle.putString(EXTRA_CURRENCY, currency); + bundle.putString(EXTRA_VALUE, value); + return bundle; + } } /** The class that corresponds mojom.PaymentItem, with minimally required fields. */ @@ -39,6 +85,27 @@ public final class WebPaymentIntentHelperType { public PaymentItem(PaymentCurrencyAmount amount) { this.amount = amount; } + /** + * Serializes this object into the provided json writer after adding an empty string for the + * redacted "label" field. + * @param json The json writer used for serialization + */ + public void serializeAndRedact(JsonWriter json) throws IOException { + // item {{{ + json.beginObject(); + // Redact the total label, because the payment app does not need it to complete the + // transaction. Matches the behavior of: + // https://w3c.github.io/payment-handler/#total-attribute + json.name("label").value(""); + + // amount {{{ + json.name("amount"); + amount.serialize(json); + // }}} amount + + json.endObject(); + // }}} item + } } /** The class that corresponds mojom.PaymentDetailsModifier, with minimally required fields. */ @@ -49,6 +116,56 @@ public final class WebPaymentIntentHelperType { this.total = total; this.methodData = methodData; } + + /** + * Serializes payment details modifiers. + * @param modifiers The collection of details modifiers to serialize. + * @return The serialized payment details modifiers + */ + public static String serializeModifiers(Collection<PaymentDetailsModifier> modifiers) { + StringWriter stringWriter = new StringWriter(); + JsonWriter json = new JsonWriter(stringWriter); + try { + json.beginArray(); + for (PaymentDetailsModifier modifier : modifiers) { + checkNotNull(modifier, "PaymentDetailsModifier"); + modifier.serialize(json); + } + json.endArray(); + } catch (IOException e) { + return EMPTY_JSON_DATA; + } + return stringWriter.toString(); + } + + private void serialize(JsonWriter json) throws IOException { + // {{{ + json.beginObject(); + + // total {{{ + if (total != null) { + json.name("total"); + total.serializeAndRedact(json); + } else { + json.name("total").nullValue(); + } + // }}} total + + // TODO(https://crbug.com/754779): The supportedMethods field was already changed from + // array to string but we should keep backward-compatibility for now. supportedMethods + // {{{ + json.name("supportedMethods").beginArray(); + json.value(methodData.supportedMethod); + json.endArray(); + // }}} supportedMethods + + // data {{{ + json.name("data").value(methodData.stringifiedData); + // }}} + + json.endObject(); + // }}} + } } /** The class that corresponds mojom.PaymentMethodData, with minimally required fields. */ @@ -63,19 +180,46 @@ public final class WebPaymentIntentHelperType { /** The class that mirrors mojom.PaymentShippingOption. */ public static final class PaymentShippingOption { + public static final String EXTRA_SHIPPING_OPTION_ID = "id"; + public static final String EXTRA_SHIPPING_OPTION_LABEL = "label"; + public static final String EXTRA_SHIPPING_OPTION_AMOUNT = "amount"; + public static final String EXTRA_SHIPPING_OPTION_SELECTED = "selected"; + public final String id; public final String label; - public final String amountCurrency; - public final String amountValue; + public final PaymentCurrencyAmount amount; public final boolean selected; - public PaymentShippingOption(String id, String label, String amountCurrency, - String amountValue, boolean selected) { + public PaymentShippingOption( + String id, String label, PaymentCurrencyAmount amount, boolean selected) { this.id = id; this.label = label; - this.amountCurrency = amountCurrency; - this.amountValue = amountValue; + this.amount = amount; this.selected = selected; } + + private Bundle asBundle() { + Bundle bundle = new Bundle(); + bundle.putString(EXTRA_SHIPPING_OPTION_ID, id); + bundle.putString(EXTRA_SHIPPING_OPTION_LABEL, label); + bundle.putBundle(EXTRA_SHIPPING_OPTION_AMOUNT, amount.asBundle()); + bundle.putBoolean(EXTRA_SHIPPING_OPTION_SELECTED, selected); + return bundle; + } + + /** + * Create a parcelable array of payment shipping options. + * @param shippingOptions The list of available shipping options + * @return The parcelable array of shipping options passed to the native payment app. + */ + public static Parcelable[] buildPaymentShippingOptionList( + List<PaymentShippingOption> shippingOptions) { + Parcelable[] result = new Parcelable[shippingOptions.size()]; + int index = 0; + for (PaymentShippingOption option : shippingOptions) { + result[index++] = option.asBundle(); + } + return result; + } } /** The class that mirrors mojom.PaymentOptions. */ @@ -94,22 +238,85 @@ public final class WebPaymentIntentHelperType { this.requestShipping = requestShipping; this.shippingType = shippingType; } + } + private static void checkNotNull(Object value, String name) { + if (value == null) throw new IllegalArgumentException(name + " should not be null."); + } + + /** The class that mirrors mojom.PaymentHandlerMethodData. */ + public static final class PaymentHandlerMethodData { + public static final String EXTRA_METHOD_NAME = "methodName"; + public static final String EXTRA_STRINGIFIED_DETAILS = "details"; + + public final String methodName; + public final String stringifiedData; + public PaymentHandlerMethodData(String methodName, String stringifiedData) { + this.methodName = methodName; + this.stringifiedData = stringifiedData; + } + + /* package */ Bundle asBundle() { + Bundle bundle = new Bundle(); + bundle.putString(EXTRA_METHOD_NAME, methodName); + bundle.putString(EXTRA_STRINGIFIED_DETAILS, stringifiedData); + return bundle; + } + } + + /** The class that mirrors mojom.PaymentRequestDetailsUpdate. */ + public static final class PaymentRequestDetailsUpdate { + public static final String EXTRA_TOTAL = "total"; + public static final String EXTRA_SHIPPING_OPTIONS = "shippingOptions"; + public static final String EXTRA_ERROR_MESSAGE = "error"; + public static final String EXTRA_STRINGIFIED_PAYMENT_METHOD_ERRORS = + "stringifiedPaymentMethodErrors"; + public static final String EXTRA_ADDRESS_ERRORS = "addressErrors"; + + @Nullable + public final PaymentCurrencyAmount total; + @Nullable + public final List<PaymentShippingOption> shippingOptions; + @Nullable + public final String error; + @Nullable + public final String stringifiedPaymentMethodErrors; + @Nullable + public final Bundle bundledShippingAddressErrors; + + public PaymentRequestDetailsUpdate(@Nullable PaymentCurrencyAmount total, + @Nullable List<PaymentShippingOption> shippingOptions, @Nullable String error, + @Nullable String stringifiedPaymentMethodErrors, + @Nullable Bundle bundledShippingAddressErrors) { + this.total = total; + this.shippingOptions = shippingOptions; + this.error = error; + this.stringifiedPaymentMethodErrors = stringifiedPaymentMethodErrors; + this.bundledShippingAddressErrors = bundledShippingAddressErrors; + } /** - * @return an ArrayList of stringified payment options. This should be an ArrayList vs a - * List since the |Bundle.putStringArrayList()| function used for populating - * "paymentOptions" in "Pay" intents accepts ArrayLists. + * Converts PaymentRequestDetailsUpdate to a bundle which will be passed to the invoked + * payment app. + * @return The converted PaymentRequestDetailsUpdate */ - public ArrayList<String> asStringArrayList() { - ArrayList<String> paymentOptionList = new ArrayList<>(); - if (requestPayerName) paymentOptionList.add("requestPayerName"); - if (requestPayerEmail) paymentOptionList.add("requestPayerEmail"); - if (requestPayerPhone) paymentOptionList.add("requestPayerPhone"); - if (requestShipping) { - paymentOptionList.add("requestShipping"); - paymentOptionList.add(shippingType); + public Bundle asBundle() { + Bundle bundle = new Bundle(); + if (total != null) { + bundle.putBundle(WebPaymentIntentHelper.EXTRA_TOTAL, total.asBundle()); + } + if (shippingOptions != null && !shippingOptions.isEmpty()) { + bundle.putParcelableArray(EXTRA_SHIPPING_OPTIONS, + PaymentShippingOption.buildPaymentShippingOptionList(shippingOptions)); + } + if (!TextUtils.isEmpty(error)) bundle.putString(EXTRA_ERROR_MESSAGE, error); + if (!TextUtils.isEmpty(stringifiedPaymentMethodErrors)) { + bundle.putString( + EXTRA_STRINGIFIED_PAYMENT_METHOD_ERRORS, stringifiedPaymentMethodErrors); + } + if (bundledShippingAddressErrors != null) { + bundle.putBundle(EXTRA_ADDRESS_ERRORS, bundledShippingAddressErrors); } - return paymentOptionList; + return bundle; } } } diff --git a/chromium/components/payments/content/android/java/src/org/chromium/components/payments/payment_details_update_service.aidl b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/payment_details_update_service.aidl new file mode 100644 index 00000000000..2cae1654e69 --- /dev/null +++ b/chromium/components/payments/content/android/java/src/org/chromium/components/payments/payment_details_update_service.aidl @@ -0,0 +1,6 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +interface org.chromium.components.payments.IPaymentDetailsUpdateService; +interface org.chromium.components.payments.IPaymentDetailsUpdateServiceCallback; diff --git a/chromium/components/payments/content/android/java_templates/ErrorStrings.java.tmpl b/chromium/components/payments/content/android/java_templates/ErrorStrings.java.tmpl index 2ca0ff39f71..741b2cfe928 100644 --- a/chromium/components/payments/content/android/java_templates/ErrorStrings.java.tmpl +++ b/chromium/components/payments/content/android/java_templates/ErrorStrings.java.tmpl @@ -52,6 +52,9 @@ public abstract class ErrorStrings {{ public static final String MINIMAL_UI_SUPPRESSED = "Payment minimal UI suppressed."; + public static final String UNATHORIZED_SERVICE_REQUEST = + "Caller's signuature or package name does not match invoked app's."; + // Prevent instantiation. private ErrorStrings() {{}} }} diff --git a/chromium/components/payments/content/android/jni_payment_app.cc b/chromium/components/payments/content/android/jni_payment_app.cc new file mode 100644 index 00000000000..621eacc557c --- /dev/null +++ b/chromium/components/payments/content/android/jni_payment_app.cc @@ -0,0 +1,254 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/payments/content/android/jni_payment_app.h" + +#include <string> +#include <utility> +#include <vector> + +#include "base/android/jni_array.h" +#include "base/android/jni_string.h" +#include "base/callback.h" +#include "components/payments/content/android/byte_buffer_helper.h" +#include "components/payments/content/android/jni_headers/JniPaymentApp_jni.h" +#include "components/payments/content/android/payment_handler_host.h" +#include "components/payments/content/payment_request_converter.h" +#include "components/payments/core/payment_method_data.h" +#include "third_party/blink/public/mojom/payments/payment_request.mojom.h" +#include "ui/gfx/android/java_bitmap.h" + +namespace payments { +namespace { + +using ::base::android::AttachCurrentThread; +using ::base::android::ConvertJavaStringToUTF8; +using ::base::android::ConvertUTF16ToJavaString; +using ::base::android::ConvertUTF8ToJavaString; +using ::base::android::JavaParamRef; +using ::base::android::ScopedJavaLocalRef; +using ::base::android::ToJavaArrayOfStrings; + +void OnAbortResult(const ::base::android::JavaRef<jobject>& jcallback, + bool aborted) { + Java_JniPaymentApp_onAbortResult(AttachCurrentThread(), jcallback, aborted); +} + +} // namespace + +// static +ScopedJavaLocalRef<jobject> JniPaymentApp::Create( + JNIEnv* env, + std::unique_ptr<PaymentApp> payment_app) { + // The |app| is owned by JniPaymentApp.java and will be destroyed through a + // JniPaymentApp::FreeNativeObject() call. + JniPaymentApp* app = new JniPaymentApp(std::move(payment_app)); + + ScopedJavaLocalRef<jobject> icon; + if (app->payment_app_->icon_bitmap() && + !app->payment_app_->icon_bitmap()->drawsNothing()) { + icon = gfx::ConvertToJavaBitmap(app->payment_app_->icon_bitmap()); + } + + return Java_JniPaymentApp_Constructor( + env, ConvertUTF8ToJavaString(env, app->payment_app_->GetId()), + ConvertUTF16ToJavaString(env, app->payment_app_->GetLabel()), + ConvertUTF16ToJavaString(env, app->payment_app_->GetSublabel()), icon, + static_cast<jint>(app->payment_app_->type()), + reinterpret_cast<jlong>(app)); +} + +ScopedJavaLocalRef<jobjectArray> JniPaymentApp::GetInstrumentMethodNames( + JNIEnv* env) { + return ToJavaArrayOfStrings( + env, std::vector<std::string>(payment_app_->GetAppMethodNames().begin(), + payment_app_->GetAppMethodNames().end())); +} + +bool JniPaymentApp::IsValidForPaymentMethodData( + JNIEnv* env, + const JavaParamRef<jstring>& jmethod, + const JavaParamRef<jobject>& jdata_byte_buffer) { + if (!jdata_byte_buffer) { + bool is_valid = false; + payment_app_->IsValidForPaymentMethodIdentifier( + ConvertJavaStringToUTF8(env, jmethod), &is_valid); + return is_valid; + } + + mojom::PaymentMethodDataPtr mojo_data; + bool success = android::DeserializeFromJavaByteBuffer(env, jdata_byte_buffer, + &mojo_data); + DCHECK(success); + + PaymentMethodData data = ConvertPaymentMethodData(mojo_data); + return payment_app_->IsValidForModifier( + ConvertJavaStringToUTF8(env, jmethod), !data.supported_networks.empty(), + std::set<std::string>(data.supported_networks.begin(), + data.supported_networks.end())); +} + +bool JniPaymentApp::HandlesShippingAddress(JNIEnv* env) { + return payment_app_->HandlesShippingAddress(); +} + +bool JniPaymentApp::HandlesPayerName(JNIEnv* env) { + return payment_app_->HandlesPayerName(); +} + +bool JniPaymentApp::HandlesPayerEmail(JNIEnv* env) { + return payment_app_->HandlesPayerEmail(); +} + +bool JniPaymentApp::HandlesPayerPhone(JNIEnv* env) { + return payment_app_->HandlesPayerPhone(); +} + +ScopedJavaLocalRef<jstring> JniPaymentApp::GetCountryCode(JNIEnv* env) { + // Only autofill payment apps have country code. + return nullptr; +} + +bool JniPaymentApp::CanMakePayment(JNIEnv* env) { + // PaymentRequestImpl.java uses this value to determine whether + // PaymentRequest.hasEnrolledInstrument() should return true. + return payment_app_->HasEnrolledInstrument(); +} + +bool JniPaymentApp::CanPreselect(JNIEnv* env) { + return payment_app_->CanPreselect(); +} + +bool JniPaymentApp::IsUserGestureRequiredToSkipUi(JNIEnv* env) { + // All payment apps require a user gesture to skip UI by default. + return true; +} + +void JniPaymentApp::InvokePaymentApp(JNIEnv* env, + const JavaParamRef<jobject>& jcallback) { + invoke_callback_ = jcallback; + payment_app_->InvokePaymentApp(/*delegate=*/this); +} + +void JniPaymentApp::UpdateWith( + JNIEnv* env, + const JavaParamRef<jobject>& jresponse_byte_buffer) { + mojom::PaymentRequestDetailsUpdatePtr response; + bool success = android::DeserializeFromJavaByteBuffer( + env, jresponse_byte_buffer, &response); + DCHECK(success); + payment_app_->UpdateWith(std::move(response)); +} + +void JniPaymentApp::OnPaymentDetailsNotUpdated(JNIEnv* env) { + payment_app_->OnPaymentDetailsNotUpdated(); +} + +bool JniPaymentApp::IsWaitingForPaymentDetailsUpdate(JNIEnv* env) { + return payment_app_->IsWaitingForPaymentDetailsUpdate(); +} + +void JniPaymentApp::AbortPaymentApp(JNIEnv* env, + const JavaParamRef<jobject>& jcallback) { + payment_app_->AbortPaymentApp(base::BindOnce( + &OnAbortResult, + base::android::ScopedJavaGlobalRef<jobject>(env, jcallback))); +} + +bool JniPaymentApp::IsReadyForMinimalUI(JNIEnv* env) { + return payment_app_->IsReadyForMinimalUI(); +} + +ScopedJavaLocalRef<jstring> JniPaymentApp::AccountBalance(JNIEnv* env) { + return ConvertUTF8ToJavaString(env, payment_app_->GetAccountBalance()); +} + +void JniPaymentApp::DisableShowingOwnUI(JNIEnv* env) { + payment_app_->DisableShowingOwnUI(); +} + +ScopedJavaLocalRef<jstring> JniPaymentApp::GetApplicationIdentifierToHide( + JNIEnv* env) { + return ConvertUTF8ToJavaString( + env, payment_app_->GetApplicationIdentifierToHide()); +} + +ScopedJavaLocalRef<jobjectArray> +JniPaymentApp::GetApplicationIdentifiersThatHideThisApp(JNIEnv* env) { + const std::set<std::string>& ids = + payment_app_->GetApplicationIdentifiersThatHideThisApp(); + return ToJavaArrayOfStrings(env, + std::vector<std::string>(ids.begin(), ids.end())); +} + +jlong JniPaymentApp::GetUkmSourceId(JNIEnv* env) { + return payment_app_->UkmSourceId(); +} + +void JniPaymentApp::SetPaymentHandlerHost( + JNIEnv* env, + const JavaParamRef<jobject>& jpayment_handler_host) { + payment_app_->SetPaymentHandlerHost( + android::PaymentHandlerHost::FromJavaPaymentHandlerHost( + env, jpayment_handler_host)); +} + +void JniPaymentApp::FreeNativeObject(JNIEnv* env) { + delete this; +} + +void JniPaymentApp::OnInstrumentDetailsReady( + const std::string& method_name, + const std::string& stringified_details, + const PayerData& payer_data) { + JNIEnv* env = AttachCurrentThread(); + + ScopedJavaLocalRef<jobject> jshipping_address = + payer_data.shipping_address + ? Java_JniPaymentApp_createShippingAddress( + env, + ConvertUTF8ToJavaString(env, + payer_data.shipping_address->country), + ToJavaArrayOfStrings(env, + payer_data.shipping_address->address_line), + ConvertUTF8ToJavaString(env, + payer_data.shipping_address->region), + ConvertUTF8ToJavaString(env, payer_data.shipping_address->city), + ConvertUTF8ToJavaString( + env, payer_data.shipping_address->dependent_locality), + ConvertUTF8ToJavaString( + env, payer_data.shipping_address->postal_code), + ConvertUTF8ToJavaString( + env, payer_data.shipping_address->sorting_code), + ConvertUTF8ToJavaString( + env, payer_data.shipping_address->organization), + ConvertUTF8ToJavaString(env, + payer_data.shipping_address->recipient), + ConvertUTF8ToJavaString(env, + payer_data.shipping_address->phone)) + : nullptr; + + ScopedJavaLocalRef<jobject> jpayer_data = Java_JniPaymentApp_createPayerData( + env, ConvertUTF8ToJavaString(env, payer_data.payer_name), + ConvertUTF8ToJavaString(env, payer_data.payer_phone), + ConvertUTF8ToJavaString(env, payer_data.payer_email), jshipping_address, + ConvertUTF8ToJavaString(env, payer_data.selected_shipping_option_id)); + + Java_JniPaymentApp_onInvokeResult( + env, invoke_callback_, ConvertUTF8ToJavaString(env, method_name), + ConvertUTF8ToJavaString(env, stringified_details), jpayer_data); +} + +void JniPaymentApp::OnInstrumentDetailsError(const std::string& error_message) { + JNIEnv* env = AttachCurrentThread(); + Java_JniPaymentApp_onInvokeError(env, invoke_callback_, + ConvertUTF8ToJavaString(env, error_message)); +} + +JniPaymentApp::JniPaymentApp(std::unique_ptr<PaymentApp> payment_app) + : payment_app_(std::move(payment_app)) {} + +JniPaymentApp::~JniPaymentApp() = default; + +} // namespace payments diff --git a/chromium/components/payments/content/android/jni_payment_app.h b/chromium/components/payments/content/android/jni_payment_app.h new file mode 100644 index 00000000000..16ffaced8f1 --- /dev/null +++ b/chromium/components/payments/content/android/jni_payment_app.h @@ -0,0 +1,101 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_PAYMENTS_CONTENT_ANDROID_JNI_PAYMENT_APP_H_ +#define COMPONENTS_PAYMENTS_CONTENT_ANDROID_JNI_PAYMENT_APP_H_ + +#include <jni.h> +#include <memory> + +#include "base/android/scoped_java_ref.h" +#include "components/payments/content/payment_app.h" + +namespace payments { + +// Forwarding calls to a PaymentApp. Owned by JniPaymentApp.java. +class JniPaymentApp : public PaymentApp::Delegate { + public: + static base::android::ScopedJavaLocalRef<jobject> Create( + JNIEnv* env, + std::unique_ptr<PaymentApp> payment_app); + + // Disallow copy and assign. + JniPaymentApp(const JniPaymentApp& other) = delete; + JniPaymentApp& operator=(const JniPaymentApp& other) = delete; + + base::android::ScopedJavaLocalRef<jobjectArray> GetInstrumentMethodNames( + JNIEnv* env); + + bool IsValidForPaymentMethodData( + JNIEnv* env, + const base::android::JavaParamRef<jstring>& jmethod, + const base::android::JavaParamRef<jobject>& jdata_byte_buffer); + + bool HandlesShippingAddress(JNIEnv* env); + + bool HandlesPayerName(JNIEnv* env); + + bool HandlesPayerEmail(JNIEnv* env); + + bool HandlesPayerPhone(JNIEnv* env); + + base::android::ScopedJavaLocalRef<jstring> GetCountryCode(JNIEnv* env); + + bool CanMakePayment(JNIEnv* env); + + bool CanPreselect(JNIEnv* env); + + bool IsUserGestureRequiredToSkipUi(JNIEnv* env); + + void InvokePaymentApp(JNIEnv* env, + const base::android::JavaParamRef<jobject>& jcallback); + + void UpdateWith( + JNIEnv* env, + const base::android::JavaParamRef<jobject>& jresponse_byte_buffer); + + void OnPaymentDetailsNotUpdated(JNIEnv* env); + + bool IsWaitingForPaymentDetailsUpdate(JNIEnv* env); + + void AbortPaymentApp(JNIEnv* env, + const base::android::JavaParamRef<jobject>& jcallback); + + bool IsReadyForMinimalUI(JNIEnv* env); + + base::android::ScopedJavaLocalRef<jstring> AccountBalance(JNIEnv* env); + + void DisableShowingOwnUI(JNIEnv* env); + + base::android::ScopedJavaLocalRef<jstring> GetApplicationIdentifierToHide( + JNIEnv* env); + + base::android::ScopedJavaLocalRef<jobjectArray> + GetApplicationIdentifiersThatHideThisApp(JNIEnv* env); + + jlong GetUkmSourceId(JNIEnv* env); + + void SetPaymentHandlerHost( + JNIEnv* env, + const base::android::JavaParamRef<jobject>& jpayment_handler_host); + + void FreeNativeObject(JNIEnv* env); + + private: + // PaymentApp::Delegate implementation: + void OnInstrumentDetailsReady(const std::string& method_name, + const std::string& stringified_details, + const PayerData& payer_data) override; + void OnInstrumentDetailsError(const std::string& error_message) override; + + explicit JniPaymentApp(std::unique_ptr<PaymentApp> payment_app); + ~JniPaymentApp() override; + + std::unique_ptr<PaymentApp> payment_app_; + base::android::ScopedJavaGlobalRef<jobject> invoke_callback_; +}; + +} // namespace payments + +#endif // COMPONENTS_PAYMENTS_CONTENT_ANDROID_JNI_PAYMENT_APP_H_ diff --git a/chromium/components/payments/content/android/payment_feature_list.cc b/chromium/components/payments/content/android/payment_feature_list.cc new file mode 100644 index 00000000000..c21c16bac3b --- /dev/null +++ b/chromium/components/payments/content/android/payment_feature_list.cc @@ -0,0 +1,69 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/payments/content/android/payment_feature_list.h" + +#include "base/android/jni_string.h" +#include "base/feature_list.h" +#include "base/notreached.h" +#include "components/payments/content/android/jni_headers/PaymentFeatureList_jni.h" +#include "components/payments/core/features.h" +#include "content/public/common/content_features.h" + +namespace payments { +namespace android { +namespace { + +// Array of payment features exposed through the Java PaymentFeatureList API. +// Entries in this array refer to features defined in +// components/payments/core/features.h, content/public/common/content_features.h +// or the .h file (for Android only features). +const base::Feature* kFeaturesExposedToJava[] = { + &::features::kServiceWorkerPaymentApps, + &::features::kWebPayments, + &::features::kWebPaymentsMinimalUI, + &features::kAlwaysAllowJustInTimePaymentApp, + &features::kAppStoreBillingDebug, + &features::kPaymentRequestSkipToGPay, + &features::kPaymentRequestSkipToGPayIfNoCard, + &features::kReturnGooglePayInBasicCard, + &features::kStrictHasEnrolledAutofillInstrument, + &features::kWebPaymentsExperimentalFeatures, + &features::kWebPaymentsMethodSectionOrderV2, + &features::kWebPaymentsModifiers, + &features::kWebPaymentsRedactShippingAddress, + &features::kWebPaymentsSingleAppUiSkip, + &kAndroidAppPaymentUpdateEvents, + &kScrollToExpandPaymentHandler, +}; + +const base::Feature* FindFeatureExposedToJava(const std::string& feature_name) { + for (size_t i = 0; i < base::size(kFeaturesExposedToJava); ++i) { + if (kFeaturesExposedToJava[i]->name == feature_name) + return kFeaturesExposedToJava[i]; + } + NOTREACHED() << "Queried feature cannot be found in PaymentsFeatureList: " + << feature_name; + return nullptr; +} + +} // namespace + +// Android only features. +const base::Feature kAndroidAppPaymentUpdateEvents{ + "AndroidAppPaymentUpdateEvents", base::FEATURE_ENABLED_BY_DEFAULT}; +// TODO(crbug.com/1094549): clean up after being stable. +const base::Feature kScrollToExpandPaymentHandler{ + "ScrollToExpandPaymentHandler", base::FEATURE_ENABLED_BY_DEFAULT}; + +static jboolean JNI_PaymentFeatureList_IsEnabled( + JNIEnv* env, + const base::android::JavaParamRef<jstring>& jfeature_name) { + const base::Feature* feature = FindFeatureExposedToJava( + base::android::ConvertJavaStringToUTF8(env, jfeature_name)); + return base::FeatureList::IsEnabled(*feature); +} + +} // namespace android +} // namespace payments diff --git a/chromium/components/payments/content/android/payment_feature_list.h b/chromium/components/payments/content/android/payment_feature_list.h new file mode 100644 index 00000000000..2d10c369def --- /dev/null +++ b/chromium/components/payments/content/android/payment_feature_list.h @@ -0,0 +1,21 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_PAYMENTS_CONTENT_ANDROID_PAYMENT_FEATURE_LIST_H_ +#define COMPONENTS_PAYMENTS_CONTENT_ANDROID_PAYMENT_FEATURE_LIST_H_ + +#include <base/feature_list.h> +#include <jni.h> + +namespace payments { +namespace android { + +// Android only payment features in alphabetical order: +extern const base::Feature kAndroidAppPaymentUpdateEvents; +extern const base::Feature kScrollToExpandPaymentHandler; + +} // namespace android +} // namespace payments + +#endif // COMPONENTS_PAYMENTS_CONTENT_ANDROID_PAYMENT_FEATURE_LIST_H_ diff --git a/chromium/components/payments/content/android/payment_handler_host.cc b/chromium/components/payments/content/android/payment_handler_host.cc index 2f2f552088f..0a5cabcdb0d 100644 --- a/chromium/components/payments/content/android/payment_handler_host.cc +++ b/chromium/components/payments/content/android/payment_handler_host.cc @@ -19,18 +19,28 @@ namespace android { jlong JNI_PaymentHandlerHost_Init( JNIEnv* env, const base::android::JavaParamRef<jobject>& web_contents, - const base::android::JavaParamRef<jobject>& delegate) { + const base::android::JavaParamRef<jobject>& listener) { return reinterpret_cast<intptr_t>( - new PaymentHandlerHost(web_contents, delegate)); + new PaymentHandlerHost(web_contents, listener)); +} + +// static +base::WeakPtr<payments::PaymentHandlerHost> +PaymentHandlerHost::FromJavaPaymentHandlerHost( + JNIEnv* env, + const base::android::JavaParamRef<jobject>& payment_handler_host) { + return reinterpret_cast<PaymentHandlerHost*>( + Java_PaymentHandlerHost_getNativeBridge(env, payment_handler_host)) + ->payment_handler_host_.AsWeakPtr(); } PaymentHandlerHost::PaymentHandlerHost( const base::android::JavaParamRef<jobject>& web_contents, - const base::android::JavaParamRef<jobject>& delegate) - : delegate_(delegate), + const base::android::JavaParamRef<jobject>& listener) + : listener_(listener), payment_handler_host_( content::WebContents::FromJavaWebContents(web_contents), - /*delegate=*/this) {} + /*delegate=*/&listener_) {} PaymentHandlerHost::~PaymentHandlerHost() {} @@ -39,10 +49,6 @@ jboolean PaymentHandlerHost::IsWaitingForPaymentDetailsUpdate( return payment_handler_host_.is_waiting_for_payment_details_update(); } -jlong PaymentHandlerHost::GetNativePaymentHandlerHost(JNIEnv* env) { - return reinterpret_cast<intptr_t>(&payment_handler_host_); -} - void PaymentHandlerHost::Destroy(JNIEnv* env) { delete this; } @@ -62,49 +68,5 @@ void PaymentHandlerHost::OnPaymentDetailsNotUpdated(JNIEnv* env) { payment_handler_host_.OnPaymentDetailsNotUpdated(); } -bool PaymentHandlerHost::ChangePaymentMethod( - const std::string& method_name, - const std::string& stringified_data) { - JNIEnv* env = base::android::AttachCurrentThread(); - return Java_PaymentHandlerHostDelegate_changePaymentMethodFromPaymentHandler( - env, delegate_, base::android::ConvertUTF8ToJavaString(env, method_name), - base::android::ConvertUTF8ToJavaString(env, stringified_data)); -} - -bool PaymentHandlerHost::ChangeShippingOption( - const std::string& shipping_option_id) { - JNIEnv* env = base::android::AttachCurrentThread(); - return Java_PaymentHandlerHostDelegate_changeShippingOptionFromPaymentHandler( - env, delegate_, - base::android::ConvertUTF8ToJavaString(env, shipping_option_id)); -} - -bool PaymentHandlerHost::ChangeShippingAddress( - mojom::PaymentAddressPtr shipping_address) { - JNIEnv* env = base::android::AttachCurrentThread(); - base::android::ScopedJavaLocalRef<jobject> jshipping_address = - Java_PaymentHandlerHost_createShippingAddress( - env, - base::android::ConvertUTF8ToJavaString(env, - shipping_address->country), - base::android::ToJavaArrayOfStrings(env, - shipping_address->address_line), - base::android::ConvertUTF8ToJavaString(env, shipping_address->region), - base::android::ConvertUTF8ToJavaString(env, shipping_address->city), - base::android::ConvertUTF8ToJavaString( - env, shipping_address->dependent_locality), - base::android::ConvertUTF8ToJavaString(env, - shipping_address->postal_code), - base::android::ConvertUTF8ToJavaString( - env, shipping_address->sorting_code), - base::android::ConvertUTF8ToJavaString( - env, shipping_address->organization), - base::android::ConvertUTF8ToJavaString(env, - shipping_address->recipient), - base::android::ConvertUTF8ToJavaString(env, shipping_address->phone)); - return Java_PaymentHandlerHostDelegate_changeShippingAddressFromPaymentHandler( - env, delegate_, jshipping_address); -} - } // namespace android } // namespace payments diff --git a/chromium/components/payments/content/android/payment_handler_host.h b/chromium/components/payments/content/android/payment_handler_host.h index ef520967a6d..186a2591d7e 100644 --- a/chromium/components/payments/content/android/payment_handler_host.h +++ b/chromium/components/payments/content/android/payment_handler_host.h @@ -9,6 +9,8 @@ #include "base/android/scoped_java_ref.h" #include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "components/payments/content/android/payment_request_update_event_listener.h" #include "components/payments/content/payment_handler_host.h" namespace payments { @@ -17,39 +19,48 @@ namespace android { // The native bridge for Java to interact with the payment handler host. // Object relationship diagram: // -// PaymentRequestImpl.java ---- implements ----> PaymentHandlerHostDelegate +// PaymentRequestImpl.java --- implements ---> PaymentRequestUpdateEventListener // | ^ -// owns |_________ -// | | -// v | -// PaymentHandlerHost.java | -// | | -// owns | -// | delegate -// v | -// android/payment_handler_host.h -- implements -> PaymentHandlerHost::Delegate -// | ^ -// owns | +// owns |________________________ +// | | +// v | +// PaymentHandlerHost.java | +// | | +// owns | +// | listener +// v | +// android/payment_handler_host.h | +// | | | +// owns | | +// | owns | +// | | | +// | v | +// | android/payment_request_update_event_listener.h +// | ^ \ ---- implements ---> PaymentHandlerHost::Delegate +// | | // | delegate // v | // payment_handler_host.h -class PaymentHandlerHost : public payments::PaymentHandlerHost::Delegate { +class PaymentHandlerHost { public: - // The |delegate| must implement PaymentHandlerHostDelegate from - // PaymentHandlerHost.java. The |web_contents| should be from the same browser - // context as the payment handler and are used for logging in developr tools. + // Converts a Java PaymentHandlerHost object into a C++ cross-platform + // payments::PaymentHandlerHost object. The returned object is ultimately + // owned by the Java PaymentHandlerHost. + static base::WeakPtr<payments::PaymentHandlerHost> FromJavaPaymentHandlerHost( + JNIEnv* env, + const base::android::JavaParamRef<jobject>& payment_handler_host); + + // The |listener| must implement PaymentRequestUpdateEventListener. The + // |web_contents| should be from the same browser context as the payment + // handler and are used for logging in developr tools. PaymentHandlerHost(const base::android::JavaParamRef<jobject>& web_contents, - const base::android::JavaParamRef<jobject>& delegate); - ~PaymentHandlerHost() override; + const base::android::JavaParamRef<jobject>& listener); + ~PaymentHandlerHost(); // Checks whether any payment method, shipping address or shipping option // change is currently in progress. jboolean IsWaitingForPaymentDetailsUpdate(JNIEnv* env) const; - // Returns the pointer to the payments::PaymentHandlerHost for binding to its - // IPC endpoint in service_worker_payment_app_bridge.cc. - jlong GetNativePaymentHandlerHost(JNIEnv* env); - // Destroys this object. void Destroy(JNIEnv* env); @@ -64,14 +75,7 @@ class PaymentHandlerHost : public payments::PaymentHandlerHost::Delegate { void OnPaymentDetailsNotUpdated(JNIEnv* env); private: - // PaymentHandlerHost::Delegate implementation: - bool ChangePaymentMethod(const std::string& method_name, - const std::string& stringified_data) override; - bool ChangeShippingOption(const std::string& shipping_option_id) override; - bool ChangeShippingAddress( - mojom::PaymentAddressPtr shipping_address) override; - - base::android::ScopedJavaGlobalRef<jobject> delegate_; + PaymentRequestUpdateEventListener listener_; payments::PaymentHandlerHost payment_handler_host_; DISALLOW_COPY_AND_ASSIGN(PaymentHandlerHost); diff --git a/chromium/components/payments/content/android/payment_request_spec.cc b/chromium/components/payments/content/android/payment_request_spec.cc new file mode 100644 index 00000000000..8c26aceeef8 --- /dev/null +++ b/chromium/components/payments/content/android/payment_request_spec.cc @@ -0,0 +1,90 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/payments/content/android/payment_request_spec.h" + +#include <utility> + +#include "base/android/jni_android.h" +#include "base/android/jni_array.h" +#include "base/android/jni_string.h" +#include "components/payments/content/android/byte_buffer_helper.h" +#include "components/payments/content/android/jni_headers/PaymentRequestSpec_jni.h" +#include "third_party/blink/public/mojom/payments/payment_request.mojom.h" + +namespace payments { +namespace android { + +// static +jlong JNI_PaymentRequestSpec_Create( + JNIEnv* env, + const base::android::JavaParamRef<jobject>& joptions_byte_buffer, + const base::android::JavaParamRef<jobject>& jdetails_byte_buffer, + const base::android::JavaParamRef<jobjectArray>& jmethod_data_byte_buffers, + const base::android::JavaParamRef<jstring>& japp_locale) { + mojom::PaymentOptionsPtr options; + bool success = + DeserializeFromJavaByteBuffer(env, joptions_byte_buffer, &options); + DCHECK(success); + + mojom::PaymentDetailsPtr details; + success = DeserializeFromJavaByteBuffer(env, jdetails_byte_buffer, &details); + DCHECK(success); + + std::vector<mojom::PaymentMethodDataPtr> method_data; + success = DeserializeFromJavaByteBufferArray(env, jmethod_data_byte_buffers, + &method_data); + DCHECK(success); + + return reinterpret_cast<intptr_t>( + new PaymentRequestSpec(std::make_unique<payments::PaymentRequestSpec>( + std::move(options), std::move(details), std::move(method_data), + /*delegate=*/nullptr, + base::android::ConvertJavaStringToUTF8(env, japp_locale)))); +} + +// static +payments::PaymentRequestSpec* PaymentRequestSpec::FromJavaPaymentRequestSpec( + JNIEnv* env, + const base::android::JavaParamRef<jobject>& jpayment_request_spec) { + return reinterpret_cast<PaymentRequestSpec*>( + Java_PaymentRequestSpec_getNativePointer(env, + jpayment_request_spec)) + ->spec_.get(); +} + +PaymentRequestSpec::PaymentRequestSpec( + std::unique_ptr<payments::PaymentRequestSpec> spec) + : spec_(std::move(spec)) {} + +void PaymentRequestSpec::UpdateWith( + JNIEnv* env, + const base::android::JavaParamRef<jobject>& jdetails_byte_buffer) { + mojom::PaymentDetailsPtr details; + bool success = + DeserializeFromJavaByteBuffer(env, jdetails_byte_buffer, &details); + DCHECK(success); + + spec_->UpdateWith(std::move(details)); +} + +void PaymentRequestSpec::Retry( + JNIEnv* env, + const base::android::JavaParamRef<jobject>& jvalidation_errors_buffer) { + mojom::PaymentValidationErrorsPtr validation_errors; + bool success = DeserializeFromJavaByteBuffer(env, jvalidation_errors_buffer, + &validation_errors); + DCHECK(success); + + spec_->Retry(std::move(validation_errors)); +} + +void PaymentRequestSpec::Destroy(JNIEnv* env) { + delete this; +} + +PaymentRequestSpec::~PaymentRequestSpec() = default; + +} // namespace android +} // namespace payments diff --git a/chromium/components/payments/content/android/payment_request_spec.h b/chromium/components/payments/content/android/payment_request_spec.h new file mode 100644 index 00000000000..ee3ea297733 --- /dev/null +++ b/chromium/components/payments/content/android/payment_request_spec.h @@ -0,0 +1,66 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_PAYMENTS_CONTENT_ANDROID_PAYMENT_REQUEST_SPEC_H_ +#define COMPONENTS_PAYMENTS_CONTENT_ANDROID_PAYMENT_REQUEST_SPEC_H_ + +#include <jni.h> +#include <memory> + +#include "base/android/scoped_java_ref.h" +#include "components/payments/content/payment_request_spec.h" + +namespace payments { +namespace android { + +// A bridge for Android to own a C++ PaymentRequestSpec object. +// +// Object ownership diagram: +// +// PaymentRequestImpl.java +// | +// v +// PaymentRequestSpec.java +// | +// v +// android/payment_request_spec.h +// | +// v +// payment_request_spec.h +class PaymentRequestSpec { + public: + // Returns the C++ PaymentRequestSpec that is owned by the Java + // PaymentRequestSpec, or nullptr after the Java method + // PaymentRequestSpec.destroy() has been called. + static payments::PaymentRequestSpec* FromJavaPaymentRequestSpec( + JNIEnv* env, + const base::android::JavaParamRef<jobject>& jpayment_request_spec); + + // Constructs the Android bridge with the given |spec|. + explicit PaymentRequestSpec( + std::unique_ptr<payments::PaymentRequestSpec> spec); + + // Called when the renderer updates the payment details in response to, e.g., + // new shipping address. + void UpdateWith(JNIEnv* env, + const base::android::JavaParamRef<jobject>& jdetails_buffer); + + // Called when the merchant retries a failed payment. + void Retry( + JNIEnv* env, + const base::android::JavaParamRef<jobject>& jvalidation_errors_buffer); + + // Destroys this bridge. + void Destroy(JNIEnv* env); + + private: + ~PaymentRequestSpec(); + + std::unique_ptr<payments::PaymentRequestSpec> spec_; +}; + +} // namespace android +} // namespace payments + +#endif // COMPONENTS_PAYMENTS_CONTENT_ANDROID_PAYMENT_REQUEST_SPEC_H_ diff --git a/chromium/components/payments/content/android/payment_request_update_event_listener.cc b/chromium/components/payments/content/android/payment_request_update_event_listener.cc new file mode 100644 index 00000000000..195c29c6ff1 --- /dev/null +++ b/chromium/components/payments/content/android/payment_request_update_event_listener.cc @@ -0,0 +1,50 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/payments/content/android/payment_request_update_event_listener.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "components/payments/content/android/jni_headers/PaymentRequestUpdateEventListener_jni.h" + +namespace payments { +namespace android { + +PaymentRequestUpdateEventListener::PaymentRequestUpdateEventListener( + const base::android::JavaParamRef<jobject>& listener) + : listener_(listener) {} + +PaymentRequestUpdateEventListener::~PaymentRequestUpdateEventListener() {} + +bool PaymentRequestUpdateEventListener::ChangePaymentMethod( + const std::string& method_name, + const std::string& stringified_data) { + JNIEnv* env = base::android::AttachCurrentThread(); + return Java_PaymentRequestUpdateEventListener_changePaymentMethodFromInvokedApp( + env, listener_, base::android::ConvertUTF8ToJavaString(env, method_name), + base::android::ConvertUTF8ToJavaString(env, stringified_data)); +} + +bool PaymentRequestUpdateEventListener::ChangeShippingOption( + const std::string& shipping_option_id) { + JNIEnv* env = base::android::AttachCurrentThread(); + return Java_PaymentRequestUpdateEventListener_changeShippingOptionFromInvokedApp( + env, listener_, + base::android::ConvertUTF8ToJavaString(env, shipping_option_id)); +} + +bool PaymentRequestUpdateEventListener::ChangeShippingAddress( + mojom::PaymentAddressPtr shipping_address) { + std::vector<uint8_t> byte_vector = + mojom::PaymentAddress::Serialize(&shipping_address); + JNIEnv* env = base::android::AttachCurrentThread(); + return Java_PaymentRequestUpdateEventListener_changeShippingAddress( + env, listener_, + base::android::ScopedJavaLocalRef<jobject>( + env, + env->NewDirectByteBuffer(byte_vector.data(), byte_vector.size()))); +} + +} // namespace android +} // namespace payments diff --git a/chromium/components/payments/content/android/payment_request_update_event_listener.h b/chromium/components/payments/content/android/payment_request_update_event_listener.h new file mode 100644 index 00000000000..63708b059f6 --- /dev/null +++ b/chromium/components/payments/content/android/payment_request_update_event_listener.h @@ -0,0 +1,37 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_PAYMENTS_CONTENT_ANDROID_PAYMENT_REQUEST_UPDATE_EVENT_LISTENER_H_ +#define COMPONENTS_PAYMENTS_CONTENT_ANDROID_PAYMENT_REQUEST_UPDATE_EVENT_LISTENER_H_ + +#include <jni.h> + +#include "base/android/scoped_java_ref.h" +#include "components/payments/content/payment_handler_host.h" + +namespace payments { +namespace android { + +class PaymentRequestUpdateEventListener + : public payments::PaymentHandlerHost::Delegate { + public: + explicit PaymentRequestUpdateEventListener( + const base::android::JavaParamRef<jobject>& listener); + ~PaymentRequestUpdateEventListener() override; + + // PaymentHandlerHost::Delegate implementation: + bool ChangePaymentMethod(const std::string& method_name, + const std::string& stringified_data) override; + bool ChangeShippingOption(const std::string& shipping_option_id) override; + bool ChangeShippingAddress( + mojom::PaymentAddressPtr shipping_address) override; + + private: + base::android::ScopedJavaGlobalRef<jobject> listener_; +}; + +} // namespace android +} // namespace payments + +#endif // COMPONENTS_PAYMENTS_CONTENT_ANDROID_PAYMENT_REQUEST_UPDATE_EVENT_LISTENER_H_ |