Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(4195)

Unified Diff: chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java

Issue 1904553003: Java implementation of PaymentRequest mojo service (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Disable Pay button before user provided required information. Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
index b66f3b01d36925e0152aa159b48b7b614b795360..94731360ca869e0323ca6c11e0d62c9bbb8ff560 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
@@ -4,32 +4,120 @@
package org.chromium.chrome.browser.payments;
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.text.TextUtils;
+
+import org.chromium.base.Callback;
+import org.chromium.chrome.browser.autofill.PersonalDataManager;
+import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
+import org.chromium.chrome.browser.favicon.FaviconHelper;
+import org.chromium.chrome.browser.payments.ui.LineItem;
+import org.chromium.chrome.browser.payments.ui.PaymentInformation;
+import org.chromium.chrome.browser.payments.ui.PaymentOption;
+import org.chromium.chrome.browser.payments.ui.PaymentRequestUI;
+import org.chromium.chrome.browser.payments.ui.SectionInformation;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.content.browser.ContentViewCore;
import org.chromium.content_public.browser.WebContents;
import org.chromium.mojo.system.MojoException;
import org.chromium.mojom.payments.PaymentDetails;
+import org.chromium.mojom.payments.PaymentItem;
import org.chromium.mojom.payments.PaymentOptions;
import org.chromium.mojom.payments.PaymentRequest;
import org.chromium.mojom.payments.PaymentRequestClient;
+import org.chromium.mojom.payments.PaymentResponse;
+import org.chromium.mojom.payments.ShippingOption;
+import org.chromium.ui.base.WindowAndroid;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
/**
* Android implementation of the PaymentRequest service defined in
* third_party/WebKit/public/platform/modules/payments/payment_request.mojom.
*/
-public class PaymentRequestImpl implements PaymentRequest {
+public class PaymentRequestImpl implements PaymentRequest, PaymentRequestUI.Client,
+ PaymentApp.InstrumentsCallback,
Ted C 2016/04/22 03:59:37 indent 8 from the start of the previous line
please use gerrit instead 2016/04/25 19:22:31 Done.
+ PaymentInstrument.DetailsCallback {
+ /**
+ * The size for the favicon in density-independent pixels.
+ */
+ private static final int FAVICON_SIZE_DP = 24;
+
+ private Activity mContext;
+ private String mMerchantName;
+ private String mOrigin;
+ private Bitmap mFavicon;
+ private List<PaymentApp> mApps;
+ private PaymentRequestClient mClient;
+ private HashSet<String> mSupportedMethods;
Ted C 2016/04/22 03:59:38 Set/List generic versions plz
please use gerrit instead 2016/04/25 19:22:31 Done.
+ private ArrayList<LineItem> mLineItems;
+ private SectionInformation mShippingOptions;
+ private JSONObject mData;
+ private SectionInformation mShippingAddresses;
+ private List<PaymentApp> mPendingApps;
+ private List<PaymentInstrument> mPendingInstruments;
+ private SectionInformation mPaymentMethods;
+ private PaymentRequestUI mUI;
+ private Callback<PaymentInformation> mPaymentInformationCallback;
+ private Callback<SectionInformation> mPaymentMethodsCallback;
+ private boolean mClientCompletion;
+
/**
* Builds the dialog.
*
* @param webContents The web contents that have invoked the PaymentRequest API.
*/
- public PaymentRequestImpl(WebContents webContents) {}
+ public PaymentRequestImpl(WebContents webContents) {
+ if (webContents == null) return;
+
+ ContentViewCore contentViewCore = ContentViewCore.fromWebContents(webContents);
+ if (contentViewCore == null) return;
+
+ WindowAndroid window = contentViewCore.getWindowAndroid();
+ if (window == null) return;
+
+ mContext = window.getActivity().get();
+ if (mContext == null) return;
Ted C 2016/04/22 03:59:38 personal aside...we should really write a helper f
please use gerrit instead 2016/04/25 19:22:30 Acknowledged.
+
+ mMerchantName = webContents.getTitle();
+ mOrigin = webContents.getVisibleUrl();
+
+ final FaviconHelper faviconHelper = new FaviconHelper();
+ float scale = mContext.getResources().getDisplayMetrics().density;
+ faviconHelper.getLocalFaviconImageForURL(Profile.getLastUsedProfile(),
+ webContents.getVisibleUrl(), (int) (FAVICON_SIZE_DP * scale + 0.5f),
+ new FaviconHelper.FaviconImageCallback() {
+ @Override
+ public void onFaviconAvailable(Bitmap bitmap, String iconUrl) {
+ faviconHelper.destroy();
+ if (bitmap == null) return;
+ if (mUI == null) {
+ mFavicon = bitmap;
+ return;
+ }
+ mUI.setTitleBitmap(bitmap);
+ }
+ });
+
+ mApps = PaymentAppFactory.create(webContents);
+ }
/**
* Called by the renderer to provide an endpoint for callbacks.
*/
@Override
public void setClient(PaymentRequestClient client) {
- assert client != null;
- client.onError();
+ mClient = client;
Ted C 2016/04/22 03:59:37 can this be called more than once? should we asse
please use gerrit instead 2016/04/25 19:22:31 Should not be called more than once. Added the ass
+ if (mClient == null) return;
+ if (mContext == null) mClient.onError();
}
/**
@@ -37,29 +125,341 @@ public class PaymentRequestImpl implements PaymentRequest {
*/
@Override
public void show(String[] supportedMethods, PaymentDetails details, PaymentOptions options,
- String stringifiedData) {}
+ String stringifiedData) {
+ if (mClient == null) return;
+
+ if (mSupportedMethods != null) {
+ mClient.onError();
Ted C 2016/04/22 03:59:37 for debugging, we might want to log each of these
please use gerrit instead 2016/04/25 19:22:31 Done.
+ return;
+ }
+
+ mSupportedMethods = getValidatedSupportedMethods(supportedMethods);
+ if (mSupportedMethods == null) {
+ mClient.onError();
+ return;
+ }
+
+ mLineItems = getValidatedLineItems(details);
+ if (mLineItems == null) {
+ mClient.onError();
+ return;
+ }
+
+ mShippingOptions = getValidatedShippingOptions(details);
+ if (mShippingOptions == null) {
+ mClient.onError();
+ return;
+ }
+
+ mData = getValidatedData(mSupportedMethods, stringifiedData);
+ if (mData == null) {
+ mClient.onError();
+ return;
+ }
+
+ List<AutofillAddress> addresses = new LinkedList<AutofillAddress>();
+ for (AutofillProfile profile : PersonalDataManager.getInstance().getProfiles()) {
Ted C 2016/04/22 03:59:37 same comment about iterator vs for int i = 0... a
please use gerrit instead 2016/04/25 19:22:32 Done.
+ addresses.add(new AutofillAddress(profile));
+ }
+
+ if (addresses.isEmpty()) {
+ mShippingAddresses = new SectionInformation();
+ } else {
+ mShippingAddresses = new SectionInformation(0, addresses);
+ }
+
+ mPendingApps = new LinkedList<PaymentApp>(mApps);
+ mPendingInstruments = new LinkedList<PaymentInstrument>();
+ boolean isGettingInstruments = false;
+
+ for (PaymentApp app : mApps) {
+ HashSet<String> appMethods = app.getSupportedMethodNames();
+ appMethods.retainAll(mSupportedMethods);
+ if (!appMethods.isEmpty()) {
+ isGettingInstruments = true;
+ app.getInstruments(mLineItems, this);
+ }
+ }
+
+ if (!isGettingInstruments) mPaymentMethods = new SectionInformation();
+
+ boolean requestShipping = options != null && options.requestShipping;
+ mUI = new PaymentRequestUI(mContext, this, requestShipping, mMerchantName, mOrigin);
+ if (mFavicon != null) mUI.setTitleBitmap(mFavicon);
Ted C 2016/04/22 03:59:37 should we set mFavicon = null after this?
please use gerrit instead 2016/04/25 19:22:32 Done.
+ }
+
+ private HashSet<String> getValidatedSupportedMethods(String[] methods) {
Ted C 2016/04/22 03:59:37 s/HashSet/Set
please use gerrit instead 2016/04/25 19:22:31 Done.
+ if (methods == null || methods.length == 0) return null;
+
+ HashSet<String> result = new HashSet<String>();
Ted C 2016/04/22 03:59:37 when creating like this, you can do: Set<String>
please use gerrit instead 2016/04/25 19:22:31 Done.
+ for (String method : methods) {
+ if (TextUtils.isEmpty(method)) return null;
Ted C 2016/04/22 03:59:37 continue or return?
please use gerrit instead 2016/04/25 19:22:31 "return null" to indicate invalid method name.
Ted C 2016/04/25 22:49:02 I'm still somewhat surprised we don't just skip ov
please use gerrit instead 2016/04/25 23:19:24 Acknowledged.
+ result.add(method);
+ }
+
+ return result;
+ }
+
+ private ArrayList<LineItem> getValidatedLineItems(PaymentDetails details) {
Ted C 2016/04/22 03:59:37 List
please use gerrit instead 2016/04/25 19:22:31 Done.
+ if (null == details || details.items == null || details.items.length == 0) return null;
Ted C 2016/04/22 03:59:37 details == null is the more consistent thing to do
please use gerrit instead 2016/04/25 19:22:31 Done.
+
+ for (PaymentItem item : details.items) {
+ if (item == null || TextUtils.isEmpty(item.id) || TextUtils.isEmpty(item.label)
+ || item.amount == null || TextUtils.isEmpty(item.amount.currencyCode)
+ || TextUtils.isEmpty(item.amount.value)) {
+ return null;
+ }
+ }
+
+ PaymentItem total = details.items[details.items.length - 1];
+ String totalCurrencyCode = total.amount.currencyCode;
+ CurrencyStringFormatter totalFormatter = new CurrencyStringFormatter(totalCurrencyCode);
+ ArrayList<LineItem> result = new ArrayList<LineItem>(details.items.length);
+
+ for (PaymentItem item : details.items) {
Ted C 2016/04/22 03:59:37 i would do i < length - 1 and skip the == total ch
please use gerrit instead 2016/04/25 19:22:32 Done.
+ if (item == total) break;
+ String currencyCode = item.amount.currencyCode;
+ if (currencyCode.equals(totalCurrencyCode)) {
+ result.add(new LineItem(item.label, "", totalFormatter.format(item.amount.value)));
+ } else {
+ result.add(new LineItem(item.label, currencyCode,
Ted C 2016/04/22 03:59:37 yikes...we support multiple currencies in a single
please use gerrit instead 2016/04/25 19:22:31 Although the spec does not explicitly prohibit thi
+ new CurrencyStringFormatter(currencyCode).format(item.amount.value)));
+ }
+ }
+
+ result.add(new LineItem(
+ total.label, totalCurrencyCode, totalFormatter.format(total.amount.value)));
+
+ return result;
+ }
+
+ private SectionInformation getValidatedShippingOptions(PaymentDetails details) {
+ if (details.shippingOptions == null || details.shippingOptions.length == 0) {
+ return new SectionInformation();
+ }
+
+ ArrayList<PaymentOption> result = new ArrayList<PaymentOption>();
+ for (ShippingOption option : details.shippingOptions) {
+ if (option == null || TextUtils.isEmpty(option.id) || TextUtils.isEmpty(option.label)
+ || option.amount == null || TextUtils.isEmpty(option.amount.currencyCode)
+ || TextUtils.isEmpty(option.amount.value)) {
+ return null;
Ted C 2016/04/22 03:59:37 return or continue?
please use gerrit instead 2016/04/25 19:22:31 "return null" to indicate invalid shipping options
+ }
+ result.add(new PaymentOption(option.id, option.label, "", PaymentOption.NO_ICON));
+ }
+
+ return new SectionInformation(
+ result.size() == 1 ? 0 : SectionInformation.NO_SELECTION, result);
+ }
+
+ private JSONObject getValidatedData(HashSet<String> supportedMethods, String stringifiedData) {
+ if (TextUtils.isEmpty(stringifiedData)) return new JSONObject();
+
+ JSONObject result;
+ try {
+ result = new JSONObject(stringifiedData);
+ } catch (JSONException e) {
+ return null;
+ }
+
+ Iterator<String> it = result.keys();
+ while (it.hasNext()) {
+ String name = it.next();
+ if (!supportedMethods.contains(name)) return null;
Ted C 2016/04/22 03:59:37 same...return or continue?
please use gerrit instead 2016/04/25 19:22:31 "return null" to indicate invalid data. I'll add a
+ if (result.optJSONObject(name) == null) return null;
+ }
+
+ return result;
+ }
+
+ /**
+ * Called to retrieve the data to show in the initial PaymentRequest UI.
+ */
+ @Override
+ public void getDefaultPaymentInformation(Callback<PaymentInformation> callback) {
+ mPaymentInformationCallback = callback;
+ if (mPaymentMethods == null) return;
+ provideDefaultPaymentInformation();
Ted C 2016/04/22 03:59:37 in general I would avoid this indirection for now.
please use gerrit instead 2016/04/25 19:22:31 I call provideDefault() also when I've finished lo
+ }
+
+ private void provideDefaultPaymentInformation() {
+ mPaymentInformationCallback.onResult(new PaymentInformation(
+ mLineItems.get(mLineItems.size() - 1), mShippingAddresses.getSelectedItem(),
+ mShippingOptions.getSelectedItem(), mPaymentMethods.getSelectedItem()));
+ mPaymentInformationCallback = null;
+ }
+
+ @Override
+ public void getLineItems(Callback<ArrayList<LineItem>> callback) {
+ callback.onResult(mLineItems);
Ted C 2016/04/22 03:59:37 same async-ness for all the callbacks
please use gerrit instead 2016/04/25 19:22:31 Done.
+ }
+
+ @Override
+ public void getShippingAddresses(Callback<SectionInformation> callback) {
+ callback.onResult(mShippingAddresses);
+ }
+
+ @Override
+ public void getShippingOptions(Callback<SectionInformation> callback) {
+ callback.onResult(mShippingOptions);
+ }
+
+ @Override
+ public void getPaymentMethods(Callback<SectionInformation> callback) {
+ mPaymentMethodsCallback = callback;
+ if (mPaymentMethods == null) return;
+ providePaymentMethods();
+ }
+
+ private void providePaymentMethods() {
+ mPaymentMethodsCallback.onResult(mPaymentMethods);
+ mPaymentMethodsCallback = null;
+ }
+
+ @Override
+ public void onShippingAddressChanged(PaymentOption selectedShippingAddress) {
+ assert selectedShippingAddress instanceof AutofillAddress;
+ mShippingAddresses.setSelectedItem(selectedShippingAddress);
+ mClient.onShippingAddressChange(
+ ((AutofillAddress) selectedShippingAddress).toShippingAddress());
+ }
+
+ @Override
+ public void onShippingOptionChanged(PaymentOption selectedShippingOption) {
+ mShippingOptions.setSelectedItem(selectedShippingOption);
+ mClient.onShippingOptionChange(selectedShippingOption.getIdentifier());
+ }
+
+ @Override
+ public void onPaymentMethodChanged(PaymentOption selectedPaymentMethod) {
+ assert selectedPaymentMethod instanceof PaymentInstrument;
+ mPaymentMethods.setSelectedItem(selectedPaymentMethod);
+ }
+
+ @Override
+ public void onPayClicked(PaymentOption selectedShippingAddress,
+ PaymentOption selectedShippingOption, PaymentOption selectedPaymentMethod) {
+ assert selectedPaymentMethod instanceof PaymentInstrument;
+ PaymentInstrument instrument = (PaymentInstrument) selectedPaymentMethod;
+ instrument.getDetails(mMerchantName, mOrigin, mLineItems,
+ mData.optJSONObject(instrument.getMethodName()), this);
+ }
+
+ @Override
+ public void onDismiss() {
+ mClient.onError();
+ closeUI(false);
+ }
/**
* Called by the merchant website to abort the payment.
*/
@Override
- public void abort() {}
+ public void abort() {
+ closeUI(false);
+ }
/**
* Called when the merchant website has processed the payment.
*/
@Override
- public void complete(boolean success) {}
+ public void complete(boolean success) {
+ mClientCompletion = true;
+ closeUI(success);
+ }
/**
* Called when the renderer closes the Mojo connection.
*/
@Override
- public void close() {}
+ public void close() {
+ closeUI(false);
+ }
/**
* Called when the Mojo connection encounters an error.
*/
@Override
- public void onConnectionError(MojoException e) {}
+ public void onConnectionError(MojoException e) {
+ closeUI(false);
+ }
+
+ /**
+ * Called after retrieving the list of payment instruments in an app.
+ */
+ @Override
+ public void onInstrumentsReady(PaymentApp app, List<PaymentInstrument> instruments) {
+ mPendingApps.remove(app);
+
+ if (instruments != null) {
+ for (PaymentInstrument instrument : instruments) {
+ if (mSupportedMethods.contains(instrument.getMethodName())) {
+ mPendingInstruments.add(instrument);
+ } else {
+ instrument.dismiss();
+ }
+ }
+ }
+
+ if (mPendingApps.isEmpty()) {
+ if (mPendingInstruments.isEmpty()) {
+ mPaymentMethods = new SectionInformation();
+ } else {
+ mPaymentMethods = new SectionInformation(0, mPendingInstruments);
+ mPendingInstruments.clear();
+ }
+
+ if (mPaymentInformationCallback != null) provideDefaultPaymentInformation();
+ if (mPaymentMethodsCallback != null) providePaymentMethods();
+ }
+ }
+
+ /**
+ * Called after retrieving instrument details.
+ */
+ @Override
+ public void onInstrumentDetailsReady(String methodName, String stringifiedDetails) {
+ PaymentResponse response = new PaymentResponse();
+ response.methodName = methodName;
+ response.stringifiedDetails = stringifiedDetails;
+ mClient.onPaymentResponse(response);
+ }
+
+ /**
+ * Called if unable to retrieve instrument details.
+ */
+ @Override
+ public void onInstrumentDetailsError() {
+ mClient.onError();
+ closeUI(false);
+ }
+
+ /**
+ * Stop communications with the client and closes the UI.
+ */
+ private void closeUI(boolean paymentSuccess) {
+ if (mUI != null) {
+ mUI.close(paymentSuccess, new Runnable() {
+ @Override
+ public void run() {
+ if (!mClientCompletion) return;
+ if (mClient == null) return;
+ mClient.onComplete();
+ mClient = null;
+ }
+ });
+ mUI = null;
+ } else {
+ mClient = null;
+ }
+
+ if (mPaymentMethods != null) {
+ for (int i = 0; i < mPaymentMethods.getSize(); i++) {
+ PaymentOption option = mPaymentMethods.getItem(i);
+ assert option instanceof PaymentInstrument;
+ ((PaymentInstrument) option).dismiss();
+ }
+ mPaymentMethods = null;
+ }
+ }
}

Powered by Google App Engine
This is Rietveld 408576698