[Payment Request] Implement PaymentRequest.canMakePayment()
authoraestes@apple.com <aestes@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 11 Oct 2017 00:37:49 +0000 (00:37 +0000)
committeraestes@apple.com <aestes@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 11 Oct 2017 00:37:49 +0000 (00:37 +0000)
https://bugs.webkit.org/show_bug.cgi?id=178048

Reviewed by Youenn Fablet.

LayoutTests/imported/w3c:

* web-platform-tests/payment-request/payment-request-canmakepayment-method.https-expected.txt: Removed.

Source/WebCore:

Test: http/tests/paymentrequest/payment-request-canmakepayment-method.https.html

* Modules/applepay/paymentrequest/ApplePayPaymentHandler.cpp:
(WebCore::ApplePayPaymentHandler::convertData): Moved
ApplePayRequest-to-ApplePaySessionPaymentRequest conversion from here to show().
(WebCore::ApplePayPaymentHandler::show): Returned an exception if
ApplePaySessionPaymentRequest conversion fails.
(WebCore::shouldDiscloseApplePayCapability): Checked if we are in an ephimeral session or if
Settings::applePayCapabilityDisclosureAllowed() is false.
(WebCore::ApplePayPaymentHandler::canMakePayment): Called
PaymentCoordinator::canMakePayments() or PaymentCoordinator::canMakePaymentsWithActiveCard()
depending on shouldDiscloseApplePayCapability().
* Modules/applepay/paymentrequest/ApplePayPaymentHandler.h:
* Modules/applepay/paymentrequest/ApplePayRequest.h:
* Modules/applepay/paymentrequest/ApplePayRequest.idl: Defined merchantIdentifier.
* Modules/paymentrequest/PaymentHandler.h:
* Modules/paymentrequest/PaymentRequest.cpp:
(WebCore::parse): Moved JSON-parsing to here from show().
(WebCore::PaymentRequest::show): Returned the exception from PaymentHandler::show().
(WebCore::PaymentRequest::canMakePayment): For each payment method, try to create a
PaymentHandler.
For the first valid PaymentHandler, call canMakePayment() and pass a lambda that resolves
the promise.
* Modules/paymentrequest/PaymentRequest.h:
* Modules/paymentrequest/PaymentRequest.idl: Added CallWith=Document annotations to show()
and canMakePayment().

LayoutTests:

* http/tests/paymentrequest/payment-request-canmakepayment-method.https-expected.txt: Added.
* http/tests/paymentrequest/payment-request-canmakepayment-method.https.html: Added.
* http/tests/ssl/applepay/PaymentRequest.https.html:
* platform/ios-wk2/TestExpectations:
* platform/mac-wk2/TestExpectations:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@223160 268f45cc-cd09-0410-ab3c-d52691b4dbfc

19 files changed:
LayoutTests/ChangeLog
LayoutTests/http/tests/paymentrequest/payment-request-abort-method.https.html
LayoutTests/http/tests/paymentrequest/payment-request-canmakepayment-method.https-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/paymentrequest/payment-request-canmakepayment-method.https.html [new file with mode: 0644]
LayoutTests/http/tests/paymentrequest/payment-request-show-method.https.html
LayoutTests/http/tests/ssl/applepay/PaymentRequest.https.html
LayoutTests/imported/w3c/ChangeLog
LayoutTests/imported/w3c/web-platform-tests/payment-request/payment-request-canmakepayment-method.https-expected.txt [deleted file]
LayoutTests/platform/ios-wk2/TestExpectations
LayoutTests/platform/mac-wk2/TestExpectations
Source/WebCore/ChangeLog
Source/WebCore/Modules/applepay/paymentrequest/ApplePayPaymentHandler.cpp
Source/WebCore/Modules/applepay/paymentrequest/ApplePayPaymentHandler.h
Source/WebCore/Modules/applepay/paymentrequest/ApplePayRequest.h
Source/WebCore/Modules/applepay/paymentrequest/ApplePayRequest.idl
Source/WebCore/Modules/paymentrequest/PaymentHandler.h
Source/WebCore/Modules/paymentrequest/PaymentRequest.cpp
Source/WebCore/Modules/paymentrequest/PaymentRequest.h
Source/WebCore/Modules/paymentrequest/PaymentRequest.idl

index b3f26cb..6f3d08d 100644 (file)
@@ -1,3 +1,16 @@
+2017-10-10  Andy Estes  <aestes@apple.com>
+
+        [Payment Request] Implement PaymentRequest.canMakePayment()
+        https://bugs.webkit.org/show_bug.cgi?id=178048
+
+        Reviewed by Youenn Fablet.
+
+        * http/tests/paymentrequest/payment-request-canmakepayment-method.https-expected.txt: Added.
+        * http/tests/paymentrequest/payment-request-canmakepayment-method.https.html: Added.
+        * http/tests/ssl/applepay/PaymentRequest.https.html:
+        * platform/ios-wk2/TestExpectations:
+        * platform/mac-wk2/TestExpectations:
+
 2017-10-10  Joanmarie Diggs  <jdiggs@igalia.com>
 
         AX: [ATK] ARIA form role should be mapped to ATK_ROLE_LANDMARK; not ATK_ROLE_FORM
index 1834ad7..87e20ed 100644 (file)
@@ -18,6 +18,7 @@ const applePay = Object.freeze({
     supportedMethods: "https://apple.com/apple-pay",
     data: {
         version: 2,
+        merchantIdentifier: '',
         merchantCapabilities: ['supports3DS'],
         supportedNetworks: ['visa', 'masterCard'],
         countryCode: 'US',
diff --git a/LayoutTests/http/tests/paymentrequest/payment-request-canmakepayment-method.https-expected.txt b/LayoutTests/http/tests/paymentrequest/payment-request-canmakepayment-method.https-expected.txt
new file mode 100644 (file)
index 0000000..9001416
--- /dev/null
@@ -0,0 +1,8 @@
+
+PASS If request.[[state]] is "created", then return a promise that resolves to true for known method. 
+PASS If request.[[state]] is "interactive", then return a promise rejected with an "InvalidStateError" DOMException. 
+PASS If request.[[state]] is "closed", then return a promise rejected with an "InvalidStateError" DOMException. 
+PASS If payment method identifier and serialized parts are supported, resolve promise with true. 
+PASS If payment method identifier is unknown, resolve promise with false. 
+PASS Optionally, at the user agent's discretion, return a promise rejected with a "NotAllowedError" DOMException. 
+
diff --git a/LayoutTests/http/tests/paymentrequest/payment-request-canmakepayment-method.https.html b/LayoutTests/http/tests/paymentrequest/payment-request-canmakepayment-method.https.html
new file mode 100644 (file)
index 0000000..2aef6a8
--- /dev/null
@@ -0,0 +1,174 @@
+<!DOCTYPE html>
+<!--  Copyright © 2017 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang).  -->
+<!--  Copyright (C) 2017 Apple Inc. All rights reserved.  -->
+<!-- FIXME: Upstream this test to web-platform-tests/payment-request/. -->
+<meta charset="utf-8">
+<title>Tests for PaymentRequest.canMakePayment() method</title>
+<link rel="help" href="https://w3c.github.io/browser-payment-api/#show-method">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    const applePay = Object.freeze({
+    supportedMethods: "https://apple.com/apple-pay",
+    data: {
+        version: 2,
+        merchantIdentifier: "",
+        merchantCapabilities: ["supports3DS"],
+        supportedNetworks: ["visa", "masterCard"],
+        countryCode: "US",
+        currencyCode: "USD",
+    }
+});
+const defaultMethods = Object.freeze([applePay]);
+const defaultDetails = Object.freeze({
+  total: {
+    label: "Total",
+    amount: {
+      currency: "USD",
+      value: "1.00",
+    },
+  },
+});
+
+promise_test(async t => {
+  const request = new PaymentRequest(defaultMethods, defaultDetails);
+  try {
+    assert_true(
+      await request.canMakePayment(),
+      `canMakePaymentPromise should be true`
+    );
+    assert_true(
+      await request.canMakePayment(),
+      `canMakePaymentPromise should be true`
+    );
+  } catch (err) {
+    assert_equals(
+      err.name,
+      "NotAllowedError",
+      "if it throws, then it must be a NotAllowedError."
+    );
+  }
+}, `If request.[[state]] is "created", then return a promise that resolves to true for known method.`);
+
+promise_test(async t => {
+  const request = new PaymentRequest(defaultMethods, defaultDetails);
+  const acceptPromise = request.show(); // Sets state to "interactive"
+  const canMakePaymentPromise = request.canMakePayment();
+  try {
+    const result = await canMakePaymentPromise;
+    assert_true(
+      false,
+      `canMakePaymentPromise should have thrown InvalidStateError`
+    );
+  } catch (err) {
+    await promise_rejects(t, "InvalidStateError", canMakePaymentPromise);
+  } finally {
+    await request.abort();
+    await promise_rejects(t, "AbortError", acceptPromise);
+  }
+  // The state should be "closed"
+  await promise_rejects(t, "InvalidStateError", request.canMakePayment());
+}, `If request.[[state]] is "interactive", then return a promise rejected with an "InvalidStateError" DOMException.`);
+
+promise_test(async t => {
+  const request = new PaymentRequest(defaultMethods, defaultDetails);
+  const acceptPromise = request.show(); // The state is now "interactive"
+  acceptPromise.catch(() => {}); // no-op, just to silence unhandled rejection in devtools.
+  await request.abort(); // The state is now "closed"
+  await promise_rejects(t, "InvalidStateError", request.canMakePayment());
+  try {
+    const result = await request.canMakePayment();
+    assert_true(
+      false,
+      `should have thrown InvalidStateError, but instead returned "${result}"`
+    );
+  } catch (err) {
+    assert_equals(
+      err.name,
+      "InvalidStateError",
+      "must be an InvalidStateError."
+    );
+  }
+}, `If request.[[state]] is "closed", then return a promise rejected with an "InvalidStateError" DOMException.`);
+
+promise_test(async t => {
+  const request = new PaymentRequest(defaultMethods, defaultDetails);
+  assert_true(await request.canMakePayment(), "basic-card should be supported");
+}, `If payment method identifier and serialized parts are supported, resolve promise with true.`);
+
+promise_test(async t => {
+  const unsupportedMethods = [
+    "this-is-not-supported",
+    "https://not.supported",
+    "e",
+    "n6jzof05mk2g4lhxr-u-q-w1-c-i-pa-ty-bdvs9-ho-ae7-p-md8-s-wq3-h-qd-e-q-sa",
+    "a-b-q-n-s-pw0",
+    "m-u",
+    "s-l5",
+    "k9-f",
+    "m-l",
+    "u4-n-t",
+    "i488jh6-g18-fck-yb-v7-i",
+    "x-x-t-t-c34-o",
+    "https://wpt",
+    "https://wpt.fyi/",
+    "https://wpt.fyi/payment",
+    "https://wpt.fyi/payment-request",
+    "https://wpt.fyi/payment-request?",
+    "https://wpt.fyi/payment-request?this=is",
+    "https://wpt.fyi/payment-request?this=is&totally",
+    "https://wpt.fyi:443/payment-request?this=is&totally",
+    "https://wpt.fyi:443/payment-request?this=is&totally#fine",
+    "https://:@wpt.fyi:443/payment-request?this=is&totally#👍",
+    " \thttps://wpt\n ",
+    "https://xn--c1yn36f",
+    "https://點看",
+  ];
+  for (const method of unsupportedMethods) {
+    try {
+      const request = new PaymentRequest(
+        [{ supportedMethods: method }],
+        defaultDetails
+      );
+      assert_false(
+        await request.canMakePayment(),
+        `method "${method}" must not be supported`
+      );
+    } catch (err) {
+      assert_true(
+        false,
+        `Unexpected exception testing method ${method}, expected false. See error console.`
+      );
+    }
+  }
+}, `If payment method identifier is unknown, resolve promise with false.`);
+
+promise_test(async t => {
+  // This test might never actually hit its assertion, but that's allowed.
+  const request = new PaymentRequest(defaultMethods, defaultDetails);
+  for (let i = 0; i < 1000; i++) {
+    try {
+      await request.canMakePayment();
+    } catch (err) {
+      assert_equals(
+        err.name,
+        "NotAllowedError",
+        "if it throws, then it must be a NotAllowedError."
+      );
+      break;
+    }
+  }
+  for (let i = 0; i < 1000; i++) {
+    try {
+      await new PaymentRequest(defaultMethods, defaultDetails).canMakePayment();
+    } catch (err) {
+      assert_equals(
+        err.name,
+        "NotAllowedError",
+        "if it throws, then it must be a NotAllowedError."
+      );
+      break;
+    }
+  }
+}, `Optionally, at the user agent's discretion, return a promise rejected with a "NotAllowedError" DOMException.`);
+</script>
index b3f4b0a..dfd1142 100644 (file)
@@ -13,6 +13,7 @@ const applePay = Object.freeze({
     supportedMethods: "https://apple.com/apple-pay",
     data: {
         version: 2,
+        merchantIdentifier: '',
         merchantCapabilities: ['supports3DS'],
         supportedNetworks: ['visa', 'masterCard'],
         countryCode: 'US',
index 7da29f8..a7997c8 100644 (file)
@@ -16,6 +16,7 @@ function validPaymentMethod() {
         supportedMethods: 'https://apple.com/apple-pay',
         data: {
             version: 2,
+            merchantIdentifier: '',
             countryCode: 'US',
             currencyCode: 'USD',
             supportedNetworks: ['visa', 'masterCard'],
index 4cd36ee..b8567aa 100644 (file)
@@ -1,3 +1,12 @@
+2017-10-10  Andy Estes  <aestes@apple.com>
+
+        [Payment Request] Implement PaymentRequest.canMakePayment()
+        https://bugs.webkit.org/show_bug.cgi?id=178048
+
+        Reviewed by Youenn Fablet.
+
+        * web-platform-tests/payment-request/payment-request-canmakepayment-method.https-expected.txt: Removed.
+
 2017-10-09  Chris Dumez  <cdumez@apple.com>
 
         It should not be possible to submit a form that is disconnected
diff --git a/LayoutTests/imported/w3c/web-platform-tests/payment-request/payment-request-canmakepayment-method.https-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/payment-request/payment-request-canmakepayment-method.https-expected.txt
deleted file mode 100644 (file)
index 2d6a887..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-
-FAIL If request.[[state]] is "created", then return a promise that resolves to true for known method. assert_equals: if it throws, then it must be a NotAllowedError. expected "NotAllowedError" but got "Error"
-FAIL If request.[[state]] is "interactive", then return a promise rejected with an "InvalidStateError" DOMException. assert_throws: function "function () { throw e }" threw object "NotSupportedError: The operation is not supported." that is not a DOMException AbortError: property "code" is equal to 9, expected 20
-PASS If request.[[state]] is "closed", then return a promise rejected with an "InvalidStateError" DOMException. 
-FAIL If payment method identifier and serialized parts are supported, resolve promise with true. assert_true: basic-card should be supported expected true got false
-PASS If payment method identifier is unknown, resolve promise with false. 
-PASS Optionally, at the user agent's discretion, return a promise rejected with a "NotAllowedError" DOMException. 
-
index e7d29d1..0ab8426 100644 (file)
@@ -40,6 +40,7 @@ webkit.org/b/175611 imported/w3c/web-platform-tests/payment-request/allowpayment
 # skip in favor of tests in http/tests/paymentrequest
 imported/w3c/web-platform-tests/payment-request/payment-request-show-method.https.html [ WontFix ]
 imported/w3c/web-platform-tests/payment-request/payment-request-abort-method.https.html [ WontFix ]
+imported/w3c/web-platform-tests/payment-request/payment-request-canmakepayment-method.https.html [ WontFix ]
 
  # skip manual payment-request tests
 imported/w3c/web-platform-tests/payment-request/algorithms-manual.https.html [ Skip ]
index d900b40..138280d 100644 (file)
@@ -38,6 +38,7 @@ webkit.org/b/177783 imported/w3c/web-platform-tests/payment-request/rejects_if_n
 # skip in favor of tests in http/tests/paymentrequest
 imported/w3c/web-platform-tests/payment-request/payment-request-show-method.https.html [ WontFix ]
 imported/w3c/web-platform-tests/payment-request/payment-request-abort-method.https.html [ WontFix ]
+imported/w3c/web-platform-tests/payment-request/payment-request-canmakepayment-method.https.html [ WontFix ]
 
 # skip manual payment-request tests
 imported/w3c/web-platform-tests/payment-request/algorithms-manual.https.html [ Skip ]
index bb42e62..79affe5 100644 (file)
@@ -1,3 +1,37 @@
+2017-10-10  Andy Estes  <aestes@apple.com>
+
+        [Payment Request] Implement PaymentRequest.canMakePayment()
+        https://bugs.webkit.org/show_bug.cgi?id=178048
+
+        Reviewed by Youenn Fablet.
+
+        Test: http/tests/paymentrequest/payment-request-canmakepayment-method.https.html
+
+        * Modules/applepay/paymentrequest/ApplePayPaymentHandler.cpp:
+        (WebCore::ApplePayPaymentHandler::convertData): Moved
+        ApplePayRequest-to-ApplePaySessionPaymentRequest conversion from here to show().
+        (WebCore::ApplePayPaymentHandler::show): Returned an exception if
+        ApplePaySessionPaymentRequest conversion fails.
+        (WebCore::shouldDiscloseApplePayCapability): Checked if we are in an ephimeral session or if
+        Settings::applePayCapabilityDisclosureAllowed() is false.
+        (WebCore::ApplePayPaymentHandler::canMakePayment): Called
+        PaymentCoordinator::canMakePayments() or PaymentCoordinator::canMakePaymentsWithActiveCard()
+        depending on shouldDiscloseApplePayCapability().
+        * Modules/applepay/paymentrequest/ApplePayPaymentHandler.h:
+        * Modules/applepay/paymentrequest/ApplePayRequest.h:
+        * Modules/applepay/paymentrequest/ApplePayRequest.idl: Defined merchantIdentifier.
+        * Modules/paymentrequest/PaymentHandler.h:
+        * Modules/paymentrequest/PaymentRequest.cpp:
+        (WebCore::parse): Moved JSON-parsing to here from show().
+        (WebCore::PaymentRequest::show): Returned the exception from PaymentHandler::show().
+        (WebCore::PaymentRequest::canMakePayment): For each payment method, try to create a
+        PaymentHandler.
+        For the first valid PaymentHandler, call canMakePayment() and pass a lambda that resolves
+        the promise.
+        * Modules/paymentrequest/PaymentRequest.h:
+        * Modules/paymentrequest/PaymentRequest.idl: Added CallWith=Document annotations to show()
+        and canMakePayment().
+
 2017-10-10  Chris Dumez  <cdumez@apple.com>
 
         Unreviewed, really fix the build with certain SDKs.
index 9c5a209..7e9c29b 100644 (file)
 #include "ApplePayMerchantCapability.h"
 #include "ApplePaySessionPaymentRequest.h"
 #include "Document.h"
-#include "Frame.h"
 #include "JSApplePayRequest.h"
 #include "LinkIconCollector.h"
 #include "MainFrame.h"
+#include "Page.h"
 #include "PaymentContact.h"
 #include "PaymentCoordinator.h"
 #include "PaymentRequestValidator.h"
+#include "Settings.h"
 
 namespace WebCore {
 
@@ -108,7 +109,7 @@ static ApplePaySessionPaymentRequest::ShippingType convert(PaymentShippingType t
     ASSERT_NOT_REACHED();
     return ApplePaySessionPaymentRequest::ShippingType::Shipping;
 }
-    
+
 static ApplePaySessionPaymentRequest::ShippingMethod convert(const PaymentShippingOption& shippingOption)
 {
     ApplePaySessionPaymentRequest::ShippingMethod result;
@@ -125,7 +126,13 @@ ExceptionOr<void> ApplePayPaymentHandler::convertData(JSC::ExecState& execState,
     if (throwScope.exception())
         return Exception { ExistingExceptionError };
 
-    auto validatedRequest = convertAndValidate(applePayRequest.version, applePayRequest);
+    m_applePayRequest = WTFMove(applePayRequest);
+    return { };
+}
+
+ExceptionOr<void> ApplePayPaymentHandler::show(Document& document)
+{
+    auto validatedRequest = convertAndValidate(m_applePayRequest->version, *m_applePayRequest);
     if (validatedRequest.hasException())
         return validatedRequest.releaseException();
 
@@ -154,17 +161,12 @@ ExceptionOr<void> ApplePayPaymentHandler::convertData(JSC::ExecState& execState,
     if (exception.hasException())
         return exception.releaseException();
 
-    m_applePayRequest = WTFMove(request);
-    return { };
-}
-
-void ApplePayPaymentHandler::show(Document& document)
-{
     Vector<URL> linkIconURLs;
     for (auto& icon : LinkIconCollector { document }.iconsOfTypes({ LinkIconType::TouchIcon, LinkIconType::TouchPrecomposedIcon }))
         linkIconURLs.append(icon.url);
 
-    paymentCoordinator(document).beginPaymentSession(*this, document.url(), linkIconURLs, *m_applePayRequest);
+    paymentCoordinator(document).beginPaymentSession(*this, document.url(), linkIconURLs, request);
+    return { };
 }
 
 void ApplePayPaymentHandler::hide(Document& document)
@@ -172,6 +174,25 @@ void ApplePayPaymentHandler::hide(Document& document)
     paymentCoordinator(document).abortPaymentSession();
 }
 
+static bool shouldDiscloseApplePayCapability(Document& document)
+{
+    auto* page = document.page();
+    if (!page || page->usesEphemeralSession())
+        return false;
+
+    return document.settings().applePayCapabilityDisclosureAllowed();
+}
+
+void ApplePayPaymentHandler::canMakePayment(Document& document, WTF::Function<void(bool)>&& completionHandler)
+{
+    if (!shouldDiscloseApplePayCapability(document)) {
+        completionHandler(paymentCoordinator(document).canMakePayments());
+        return;
+    }
+
+    paymentCoordinator(document).canMakePaymentsWithActiveCard(m_applePayRequest->merchantIdentifier, document.domain(), WTFMove(completionHandler));
+}
+
 } // namespace WebCore
 
 #endif // ENABLE(APPLE_PAY) && ENABLE(PAYMENT_REQUEST)
index 31820d1..1e1a194 100644 (file)
@@ -27,7 +27,7 @@
 
 #if ENABLE(APPLE_PAY) && ENABLE(PAYMENT_REQUEST)
 
-#include "ApplePaySessionPaymentRequest.h"
+#include "ApplePayRequest.h"
 #include "PaymentHandler.h"
 #include "PaymentSession.h"
 #include <wtf/Noncopyable.h>
@@ -48,8 +48,9 @@ private:
 
     // PaymentHandler
     ExceptionOr<void> convertData(JSC::ExecState&, JSC::JSValue&&) final;
-    void show(Document&) final;
+    ExceptionOr<void> show(Document&) final;
     void hide(Document&) final;
+    void canMakePayment(Document&, WTF::Function<void(bool)>&& completionHandler) final;
 
     // PaymentSession
     void validateMerchant(const URL&) final { }
@@ -60,7 +61,7 @@ private:
     void didCancelPaymentSession() final { }
 
     Ref<PaymentRequest> m_paymentRequest;
-    std::optional<ApplePaySessionPaymentRequest> m_applePayRequest;
+    std::optional<ApplePayRequest> m_applePayRequest;
 };
 
 } // namespace WebCore
index b11166e..aedbe82 100644 (file)
@@ -33,6 +33,7 @@ namespace WebCore {
 
 struct ApplePayRequest : public ApplePayRequestBase {
     unsigned long version;
+    String merchantIdentifier;
 };
 
 } // namespace WebCore
index 4e73a66..fb590af 100644 (file)
@@ -27,4 +27,5 @@
     Conditional=APPLE_PAY&PAYMENT_REQUEST,
 ] dictionary ApplePayRequest : ApplePayRequestBase {
     required unsigned long version;
+    required DOMString merchantIdentifier;
 };
index 5dee546..4487607 100644 (file)
@@ -29,6 +29,7 @@
 
 #include "PaymentRequest.h"
 #include "PaymentSessionBase.h"
+#include <wtf/Function.h>
 
 namespace JSC {
 class ExecState;
@@ -45,8 +46,9 @@ public:
     static bool hasActiveSession(Document&);
 
     virtual ExceptionOr<void> convertData(JSC::ExecState&, JSC::JSValue&&) = 0;
-    virtual void show(Document&) = 0;
+    virtual ExceptionOr<void> show(Document&) = 0;
     virtual void hide(Document&) = 0;
+    virtual void canMakePayment(Document&, WTF::Function<void(bool)>&& completionHandler) = 0;
 };
 
 } // namespace WebCore
index 0ca7d49..209bfcf 100644 (file)
@@ -340,8 +340,17 @@ PaymentRequest::~PaymentRequest()
     ASSERT(!m_activePaymentHandler);
 }
 
+static ExceptionOr<JSC::JSValue> parse(ScriptExecutionContext& context, const String& string)
+{
+    auto scope = DECLARE_THROW_SCOPE(context.vm());
+    JSC::JSValue data = JSONParse(context.execState(), string);
+    if (scope.exception())
+        return Exception { ExistingExceptionError };
+    return WTFMove(data);
+}
+
 // https://www.w3.org/TR/payment-request/#show()-method
-void PaymentRequest::show(ShowPromise&& promise)
+void PaymentRequest::show(Document& document, ShowPromise&& promise)
 {
     // FIXME: Reject promise with SecurityError if show() was not triggered by a user gesture.
     // Find a way to do this without breaking the payment-request web platform tests.
@@ -351,7 +360,6 @@ void PaymentRequest::show(ShowPromise&& promise)
         return;
     }
 
-    auto& document = downcast<Document>(*scriptExecutionContext());
     if (PaymentHandler::hasActiveSession(document)) {
         promise.reject(Exception { AbortError });
         return;
@@ -363,10 +371,9 @@ void PaymentRequest::show(ShowPromise&& promise)
 
     RefPtr<PaymentHandler> selectedPaymentHandler;
     for (auto& paymentMethod : m_serializedMethodData) {
-        auto scope = DECLARE_THROW_SCOPE(document.vm());
-        JSC::JSValue data = JSONParse(document.execState(), paymentMethod.serializedData);
-        if (scope.exception()) {
-            m_showPromise->reject(Exception { ExistingExceptionError });
+        auto data = parse(document, paymentMethod.serializedData);
+        if (data.hasException()) {
+            m_showPromise->reject(data.releaseException());
             return;
         }
 
@@ -374,7 +381,7 @@ void PaymentRequest::show(ShowPromise&& promise)
         if (!handler)
             continue;
 
-        auto result = handler->convertData(*document.execState(), WTFMove(data));
+        auto result = handler->convertData(*document.execState(), data.releaseReturnValue());
         if (result.hasException()) {
             m_showPromise->reject(result.releaseException());
             return;
@@ -389,11 +396,30 @@ void PaymentRequest::show(ShowPromise&& promise)
         return;
     }
 
+    auto exception = selectedPaymentHandler->show(document);
+    if (exception.hasException()) {
+        m_showPromise->reject(exception.releaseException());
+        return;
+    }
+
     ASSERT(!m_activePaymentHandler);
     m_activePaymentHandler = WTFMove(selectedPaymentHandler);
+    setPendingActivity(this); // unsetPendingActivity() is called below in stop()
+}
+
+void PaymentRequest::stop()
+{
+    if (m_state != State::Interactive)
+        return;
 
-    m_activePaymentHandler->show(document);
-    setPendingActivity(this);
+    if (auto paymentHandler = std::exchange(m_activePaymentHandler, nullptr)) {
+        unsetPendingActivity(this);
+        paymentHandler->hide(downcast<Document>(*scriptExecutionContext()));
+    }
+
+    ASSERT(m_state == State::Interactive);
+    m_state = State::Closed;
+    m_showPromise->reject(Exception { AbortError });
 }
 
 // https://www.w3.org/TR/payment-request/#abort()-method
@@ -408,14 +434,32 @@ ExceptionOr<void> PaymentRequest::abort(AbortPromise&& promise)
 }
 
 // https://www.w3.org/TR/payment-request/#canmakepayment()-method
-void PaymentRequest::canMakePayment(CanMakePaymentPromise&& promise)
+void PaymentRequest::canMakePayment(Document& document, CanMakePaymentPromise&& promise)
 {
     if (m_state != State::Created) {
         promise.reject(Exception { InvalidStateError });
         return;
     }
 
-    // FIXME: Resolve the promise with true if we can support any of the payment methods in m_serializedMethodData.
+    for (auto& paymentMethod : m_serializedMethodData) {
+        auto data = parse(document, paymentMethod.serializedData);
+        if (data.hasException())
+            continue;
+
+        auto handler = PaymentHandler::create(*this, paymentMethod.identifier);
+        if (!handler)
+            continue;
+
+        auto exception = handler->convertData(*document.execState(), data.releaseReturnValue());
+        if (exception.hasException())
+            continue;
+
+        handler->canMakePayment(document, [promise = WTFMove(promise)](bool canMakePayment) mutable {
+            promise.resolve(canMakePayment);
+        });
+        return;
+    }
+
     promise.resolve(false);
 }
 
@@ -443,21 +487,6 @@ bool PaymentRequest::canSuspendForDocumentSuspension() const
     }
 }
 
-void PaymentRequest::stop()
-{
-    if (m_state != State::Interactive)
-        return;
-
-    if (auto paymentHandler = std::exchange(m_activePaymentHandler, nullptr)) {
-        unsetPendingActivity(this);
-        paymentHandler->hide(downcast<Document>(*scriptExecutionContext()));
-    }
-
-    ASSERT(m_state == State::Interactive);
-    m_state = State::Closed;
-    m_showPromise->reject(Exception { AbortError });
-}
-
 } // namespace WebCore
 
 #endif // ENABLE(PAYMENT_REQUEST)
index ffa213f..a89185b 100644 (file)
@@ -54,9 +54,9 @@ public:
     static ExceptionOr<Ref<PaymentRequest>> create(Document&, Vector<PaymentMethodData>&&, PaymentDetailsInit&&, PaymentOptions&&);
     ~PaymentRequest();
 
-    void show(ShowPromise&&);
+    void show(Document&, ShowPromise&&);
     ExceptionOr<void> abort(AbortPromise&&);
-    void canMakePayment(CanMakePaymentPromise&&);
+    void canMakePayment(Document&, CanMakePaymentPromise&&);
 
     const String& id() const;
     PaymentAddress* shippingAddress() const { return m_shippingAddress.get(); }
@@ -103,7 +103,6 @@ private:
     RefPtr<PaymentAddress> m_shippingAddress;
     State m_state { State::Created };
     std::optional<ShowPromise> m_showPromise;
-    std::optional<CanMakePaymentPromise> m_canMakePaymentPromise;
     RefPtr<PaymentHandler> m_activePaymentHandler;
 };
 
index 9403415..c95fe8e 100644 (file)
@@ -32,9 +32,9 @@
     EnabledBySetting=PaymentRequest,
     SecureContext
 ] interface PaymentRequest : EventTarget {
-    Promise<PaymentResponse> show();
+    [CallWith=Document] Promise<PaymentResponse> show();
     [MayThrowException] Promise<void> abort();
-    Promise<boolean> canMakePayment();
+    [CallWith=Document] Promise<boolean> canMakePayment();
 
     readonly attribute DOMString id;
     readonly attribute PaymentAddress? shippingAddress;