Skip to content
This repository has been archived by the owner on Jul 18, 2018. It is now read-only.

Commit

Permalink
[Merge M-56] Add canMakeActivePayment() method to web payments.
Browse files Browse the repository at this point in the history
canMakeActivePayment() is a proposed function on PaymentRequest object.

Proposal:
https://github.com/zkoch/zkoch.github.io/blob/master/pr-detect-avail.md

Example usage:
pr.canMakeActivePayment()
  .then(result => { if (result) return pr.show(); })
  .catch(error => { console.log(error); });

When canMakeActivePayment() is called, Chrome stores the website origin
and the payment methods that it's checking in memory. That's shared
across the whole browser, in global state. (Not storing this on disk, so
the user can clear this data via browser restart.) Then Chrome starts a
timer for 30 minutes. When the timer fires, Chrome removes that origin
and the payment methods that it was checking from the list. If the same
origin tries to check different payment methods within the 30 minute
window, Chrome rejects that request. If canMakeActivePayment() is
rejected, then pr.show() can still be called regardless.

Intent to implement and ship:
https://groups.google.com/a/chromium.org/d/msg/blink-dev/IoIxRpn6l9g/ux1C1Cj7AQAJ

Tag review:
w3ctag/design-reviews#146

OWP launch tracking bug:
http://crbug.com/664619

Link to entry on the feature dashboard:
https://www.chromestatus.com/feature/5702608073261056

The feature is behind
chrome://flags/#enable-experimental-web-platform-features until it is
approved to ship.

BUG=662931,664619

Review-Url: https://codereview.chromium.org/2467393002
Cr-Commit-Position: refs/heads/master@{#433164}
(cherry picked from commit 5132963)

Review URL: https://codereview.chromium.org/2516923002 .

Cr-Commit-Position: refs/branch-heads/2924@{#11}
Cr-Branched-From: 3a87aec-refs/heads/master@{#433059}
  • Loading branch information
rsolomakhin committed Nov 19, 2016
1 parent bacaf59 commit cb165c5
Show file tree
Hide file tree
Showing 23 changed files with 821 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
import android.app.Activity;

import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.payments.PaymentRequestImpl.PaymentRequestDismissObserver;
import org.chromium.content.browser.ContentViewCore;
import org.chromium.content_public.browser.WebContents;
import org.chromium.mojo.system.MojoException;
import org.chromium.payments.mojom.ActivePaymentQueryResult;
import org.chromium.payments.mojom.PaymentDetails;
import org.chromium.payments.mojom.PaymentErrorReason;
import org.chromium.payments.mojom.PaymentMethodData;
Expand All @@ -23,10 +23,8 @@
/**
* Creates instances of PaymentRequest.
*/
public class PaymentRequestFactory
implements InterfaceFactory<PaymentRequest>, PaymentRequestDismissObserver {
public class PaymentRequestFactory implements InterfaceFactory<PaymentRequest> {
private final WebContents mWebContents;
private boolean mIsPaymentRequestRunning;

/**
* An implementation of PaymentRequest that immediately rejects all connections.
Expand Down Expand Up @@ -58,6 +56,13 @@ public void abort() {}
@Override
public void complete(int result) {}

@Override
public void canMakeActivePayment() {
if (mClient != null) {
mClient.onCanMakeActivePayment(ActivePaymentQueryResult.CANNOT_MAKE_ACTIVE_PAYMENT);
}
}

@Override
public void close() {}

Expand Down Expand Up @@ -91,14 +96,6 @@ public PaymentRequest createImpl() {
Activity context = window.getActivity().get();
if (context == null) return new InvalidPaymentRequest();

if (mIsPaymentRequestRunning) return new InvalidPaymentRequest();
mIsPaymentRequestRunning = true;

return new PaymentRequestImpl(context, mWebContents, this);
}

@Override
public void onPaymentRequestDismissed() {
mIsPaymentRequestRunning = false;
return new PaymentRequestImpl(context, mWebContents);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import android.app.Activity;
import android.graphics.Bitmap;
import android.os.Handler;
import android.support.v4.util.ArrayMap;
import android.text.TextUtils;

import org.chromium.base.Callback;
Expand Down Expand Up @@ -37,6 +38,7 @@
import org.chromium.components.url_formatter.UrlFormatter;
import org.chromium.content_public.browser.WebContents;
import org.chromium.mojo.system.MojoException;
import org.chromium.payments.mojom.ActivePaymentQueryResult;
import org.chromium.payments.mojom.PaymentComplete;
import org.chromium.payments.mojom.PaymentDetails;
import org.chromium.payments.mojom.PaymentErrorReason;
Expand All @@ -53,7 +55,6 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
Expand All @@ -67,16 +68,6 @@
public class PaymentRequestImpl implements PaymentRequest, PaymentRequestUI.Client,
PaymentApp.InstrumentsCallback, PaymentInstrument.InstrumentDetailsCallback,
PaymentResponseHelper.PaymentResponseRequesterDelegate {
/**
* Observer to be notified when PaymentRequest UI has been dismissed.
*/
public interface PaymentRequestDismissObserver {
/**
* Called when PaymentRequest UI has been dismissed.
*/
void onPaymentRequestDismissed();
}

/**
* A test-only observer for the PaymentRequest service implementation.
*/
Expand Down Expand Up @@ -106,6 +97,11 @@ public interface PaymentRequestServiceObserverForTest {
* </ul>
*/
void onPaymentRequestServiceShowFailed();

/**
* Called when the canMakeActivePayment() request has been responded.
*/
void onPaymentRequestServiceActivePaymentQueryResponded();
}

private static final String TAG = "cr_PaymentRequest";
Expand All @@ -119,8 +115,21 @@ public int compare(Completable a, Completable b) {
}
};

/** Every origin can call canMakeActivePayment() every 30 minutes. */
private static final int CAN_MAKE_ACTIVE_PAYMENT_QUERY_PERIOD_MS = 30 * 60 * 1000;

private static PaymentRequestServiceObserverForTest sObserverForTest;

/** True if show() was called in any PaymentRequestImpl object. */
private static boolean sIsShowing;

/**
* In-memory mapping of the origins of websites that have recently called canMakeActivePayment()
* to the list of the payment methods that were been queried. Used for throttling the usage of
* this call. The user can clear the list by restarting the browser.
*/
private static Map<String, Set<String>> sCanMakeActivePaymentQueries;

/** Monitors changes in the TabModelSelector. */
private final TabModelSelectorObserver mSelectorObserver = new EmptyTabModelSelectorObserver() {
@Override
Expand All @@ -139,7 +148,6 @@ public void didSelectTab(Tab tab, TabSelectionType type, int lastId) {

private final Handler mHandler = new Handler();
private final ChromeActivity mContext;
private final PaymentRequestDismissObserver mDismissObserver;
private final String mMerchantName;
private final String mOrigin;
private final List<PaymentApp> mApps;
Expand Down Expand Up @@ -187,13 +195,11 @@ public void didSelectTab(Tab tab, TabSelectionType type, int lastId) {
private boolean mMerchantSupportsAutofillPaymentInstruments;
private ContactEditor mContactEditor;
private boolean mHasRecordedAbortReason;
private boolean mQueriedCanMakeActivePayment;

/** True if any of the requested payment methods are supported. */
private boolean mArePaymentMethodsSupported;

/** True if show() was called. */
private boolean mIsShowing;

/** The helper to create and fill the response to send to the merchant. */
private PaymentResponseHelper mPaymentResponseHelper;

Expand All @@ -202,21 +208,18 @@ public void didSelectTab(Tab tab, TabSelectionType type, int lastId) {
*
* @param context The context where PaymentRequest has been invoked.
* @param webContents The web contents that have invoked the PaymentRequest API.
* @param dismissObserver The observer to notify when PaymentRequest UI has been dismissed.
*/
public PaymentRequestImpl(Activity context, WebContents webContents,
PaymentRequestDismissObserver dismissObserver) {
public PaymentRequestImpl(Activity context, WebContents webContents) {
assert context != null;
assert webContents != null;
assert dismissObserver != null;

assert context instanceof ChromeActivity;
mContext = (ChromeActivity) context;

mDismissObserver = dismissObserver;
mMerchantName = webContents.getTitle();
// The feature is available only in secure context, so it's OK to not show HTTPS.
mOrigin = UrlFormatter.formatUrlForSecurityDisplay(webContents.getVisibleUrl(), false);
mOrigin = UrlFormatter.formatUrlForSecurityDisplay(
webContents.getLastCommittedUrl(), false);

final FaviconHelper faviconHelper = new FaviconHelper();
faviconHelper.getLocalFaviconImageForURL(Profile.getLastUsedProfile(),
Expand Down Expand Up @@ -402,9 +405,16 @@ public void init(PaymentRequestClient client, PaymentMethodData[] methodData,
*/
@Override
public void show() {
if (mClient == null || mIsShowing) return;
if (mClient == null) return;

if (getIsShowing()) {
disconnectFromClientWithDebugMessage("A PaymentRequest UI is already showing");
recordAbortReasonHistogram(
PaymentRequestMetrics.ABORT_REASON_INVALID_DATA_FROM_RENDERER);
return;
}

mIsShowing = true;
setIsShowing(true);
if (disconnectIfNoPaymentMethodsSupported()) return;

// Catch any time the user switches tabs. Because the dialog is modal, a user shouldn't be
Expand All @@ -420,7 +430,7 @@ private static Map<String, PaymentMethodData> getValidatedMethodData(
PaymentMethodData[] methodData, CardEditor paymentMethodsCollector) {
// Payment methodData are required.
if (methodData == null || methodData.length == 0) return null;
Map<String, PaymentMethodData> result = new HashMap<>();
Map<String, PaymentMethodData> result = new ArrayMap<>();
for (int i = 0; i < methodData.length; i++) {
String[] methods = methodData[i].supportedMethods;

Expand All @@ -444,7 +454,7 @@ private void getMatchingPaymentInstruments() {
mPendingInstruments = new ArrayList<>();
mPendingAutofillInstruments = new ArrayList<>();

Map<PaymentApp, Map<String, PaymentMethodData>> queryApps = new HashMap<>();
Map<PaymentApp, Map<String, PaymentMethodData>> queryApps = new ArrayMap<>();
for (int i = 0; i < mApps.size(); i++) {
PaymentApp app = mApps.get(i);
Map<String, PaymentMethodData> appMethods =
Expand Down Expand Up @@ -472,7 +482,7 @@ private static Map<String, PaymentMethodData> filterMerchantMethodData(
Map<String, PaymentMethodData> result = null;
for (String method : appMethods) {
if (merchantMethodData.containsKey(method)) {
if (result == null) result = new HashMap<>();
if (result == null) result = new ArrayMap<>();
result.put(method, merchantMethodData.get(method));
}
}
Expand Down Expand Up @@ -914,6 +924,46 @@ public void complete(int result) {
closeUI(PaymentComplete.FAIL != result);
}

/**
* Called by the merchant website to check if the user has complete payment instruments.
*/
@Override
public void canMakeActivePayment() {
if (mClient == null) return;

if (sCanMakeActivePaymentQueries == null) sCanMakeActivePaymentQueries = new ArrayMap<>();

if (sCanMakeActivePaymentQueries.containsKey(mOrigin)) {
if (!mMethodData.keySet().equals(sCanMakeActivePaymentQueries.get(mOrigin))) {
mClient.onCanMakeActivePayment(ActivePaymentQueryResult.QUERY_QUOTA_EXCEEDED);
if (sObserverForTest != null) {
sObserverForTest.onPaymentRequestServiceActivePaymentQueryResponded();
}
return;
}
} else {
sCanMakeActivePaymentQueries.put(mOrigin, mMethodData.keySet());
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
sCanMakeActivePaymentQueries.remove(mOrigin);
}
}, CAN_MAKE_ACTIVE_PAYMENT_QUERY_PERIOD_MS);
}

if (!mPendingApps.isEmpty() || !mPendingInstruments.isEmpty()) {
mQueriedCanMakeActivePayment = true;
} else {
mClient.onCanMakeActivePayment(mPaymentMethodsSection == null
|| mPaymentMethodsSection.getSelectedItem() == null
? ActivePaymentQueryResult.CANNOT_MAKE_ACTIVE_PAYMENT
: ActivePaymentQueryResult.CAN_MAKE_ACTIVE_PAYMENT);
if (sObserverForTest != null) {
sObserverForTest.onPaymentRequestServiceActivePaymentQueryResponded();
}
}
}

/**
* Called when the renderer closes the Mojo connection.
*/
Expand Down Expand Up @@ -1001,6 +1051,16 @@ public void onInstrumentsReady(PaymentApp app, List<PaymentInstrument> instrumen
}
}

if (mQueriedCanMakeActivePayment) {
mQueriedCanMakeActivePayment = false;
mClient.onCanMakeActivePayment(selection == 0
? ActivePaymentQueryResult.CAN_MAKE_ACTIVE_PAYMENT
: ActivePaymentQueryResult.CANNOT_MAKE_ACTIVE_PAYMENT);
if (sObserverForTest != null) {
sObserverForTest.onPaymentRequestServiceActivePaymentQueryResponded();
}
}

// The list of payment instruments is ready to display.
mPaymentMethodsSection = new SectionInformation(PaymentRequestUI.TYPE_PAYMENT_METHODS,
selection, mPendingInstruments);
Expand All @@ -1024,7 +1084,7 @@ private boolean disconnectIfNoPaymentMethodsSupported() {
&& !ChromeFeatureList.isEnabled(ChromeFeatureList.NO_CREDIT_CARD_ABORT);

if (!mArePaymentMethodsSupported
|| (mIsShowing && !waitingForPaymentApps && !foundPaymentMethods
|| (getIsShowing() && !waitingForPaymentApps && !foundPaymentMethods
&& !userCanAddCreditCard)) {
// All payment apps have responded, but none of them have instruments. It's possible to
// add credit cards, but the merchant does not support them either. The payment request
Expand Down Expand Up @@ -1141,7 +1201,15 @@ public void run() {
private void closeClient() {
if (mClient != null) mClient.close();
mClient = null;
mDismissObserver.onPaymentRequestDismissed();
setIsShowing(false);
}

private static boolean getIsShowing() {
return sIsShowing;
}

private static void setIsShowing(boolean isShowing) {
sIsShowing = isShowing;
}

@VisibleForTesting
Expand Down
5 changes: 5 additions & 0 deletions chrome/android/java_sources.gni
Original file line number Diff line number Diff line change
Expand Up @@ -1313,7 +1313,11 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/policy/CombinedPolicyProviderTest.java",
"javatests/src/org/chromium/chrome/browser/payments/CurrencyStringFormatterTest.java",
"javatests/src/org/chromium/chrome/browser/payments/PaymentRequestAbortTest.java",
"javatests/src/org/chromium/chrome/browser/payments/PaymentRequestActivePaymentQueryNoCardTest.java",
"javatests/src/org/chromium/chrome/browser/payments/PaymentRequestActivePaymentQueryTest.java",
"javatests/src/org/chromium/chrome/browser/payments/PaymentRequestBillingAddressTest.java",
"javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCcActivePaymentQueryNoCardTest.java",
"javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCcActivePaymentQueryTest.java",
"javatests/src/org/chromium/chrome/browser/payments/PaymentRequestContactDetailsTest.java",
"javatests/src/org/chromium/chrome/browser/payments/PaymentRequestContactDetailsAndFreeShippingTest.java",
"javatests/src/org/chromium/chrome/browser/payments/PaymentRequestDynamicShippingMultipleAddressesTest.java",
Expand All @@ -1336,6 +1340,7 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/payments/PaymentRequestNameAndFreeShippingTest.java",
"javatests/src/org/chromium/chrome/browser/payments/PaymentRequestNameTest.java",
"javatests/src/org/chromium/chrome/browser/payments/PaymentRequestNoShippingTest.java",
"javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppActivePaymentQueryTest.java",
"javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppAndCardsTest.java",
"javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppTest.java",
"javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPhoneTest.java",
Expand Down
Loading

0 comments on commit cb165c5

Please sign in to comment.