summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/payment-handler
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/payment-handler
parentInitial commit. (diff)
downloadfirefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz
firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/payment-handler')
-rw-r--r--testing/web-platform/tests/payment-handler/META.yml4
-rw-r--r--testing/web-platform/tests/payment-handler/app-can-make-payment.js58
-rw-r--r--testing/web-platform/tests/payment-handler/app-change-payment-method.js30
-rw-r--r--testing/web-platform/tests/payment-handler/app-change-shipping-address.js44
-rw-r--r--testing/web-platform/tests/payment-handler/app-change-shipping-option.js44
-rw-r--r--testing/web-platform/tests/payment-handler/app-simple.js74
-rw-r--r--testing/web-platform/tests/payment-handler/app-supports-shipping-contact-delegation.js44
-rw-r--r--testing/web-platform/tests/payment-handler/can-make-payment-event-constructor.https.html14
-rw-r--r--testing/web-platform/tests/payment-handler/can-make-payment-event-constructor.https.serviceworker.html13
-rw-r--r--testing/web-platform/tests/payment-handler/can-make-payment-event-constructor.https.serviceworker.js47
-rw-r--r--testing/web-platform/tests/payment-handler/can-make-payment-event-constructor.https.worker.js7
-rw-r--r--testing/web-platform/tests/payment-handler/can-make-payment-event.https.html264
-rw-r--r--testing/web-platform/tests/payment-handler/change-payment-method-manual.https.html170
-rw-r--r--testing/web-platform/tests/payment-handler/change-shipping-address-manual.https.html161
-rw-r--r--testing/web-platform/tests/payment-handler/change-shipping-option-manual.https.html160
-rw-r--r--testing/web-platform/tests/payment-handler/idlharness.https.any.js47
-rw-r--r--testing/web-platform/tests/payment-handler/manifest.json10
-rw-r--r--testing/web-platform/tests/payment-handler/payment-app/payment.html5
-rw-r--r--testing/web-platform/tests/payment-handler/payment-instruments.https.html379
-rw-r--r--testing/web-platform/tests/payment-handler/payment-request-event-constructor.https.html14
-rw-r--r--testing/web-platform/tests/payment-handler/payment-request-event-constructor.https.serviceworker.html13
-rw-r--r--testing/web-platform/tests/payment-handler/payment-request-event-constructor.https.serviceworker.js45
-rw-r--r--testing/web-platform/tests/payment-handler/payment-request-event-constructor.https.worker.js7
-rw-r--r--testing/web-platform/tests/payment-handler/payment-request-event-manual.https.html88
-rw-r--r--testing/web-platform/tests/payment-handler/register-and-activate-service-worker.js28
-rw-r--r--testing/web-platform/tests/payment-handler/same-object-attributes.https.html19
-rw-r--r--testing/web-platform/tests/payment-handler/supports-shipping-contact-delegation-manual.https.html88
-rw-r--r--testing/web-platform/tests/payment-handler/untrusted-event.https.html56
-rw-r--r--testing/web-platform/tests/payment-handler/untrusted-event.js59
29 files changed, 1992 insertions, 0 deletions
diff --git a/testing/web-platform/tests/payment-handler/META.yml b/testing/web-platform/tests/payment-handler/META.yml
new file mode 100644
index 0000000000..eff7624d05
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/META.yml
@@ -0,0 +1,4 @@
+spec: https://w3c.github.io/payment-handler/
+suggested_reviewers:
+ - marcoscaceres
+ - rsolomakhin
diff --git a/testing/web-platform/tests/payment-handler/app-can-make-payment.js b/testing/web-platform/tests/payment-handler/app-can-make-payment.js
new file mode 100644
index 0000000000..cd27bce02e
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/app-can-make-payment.js
@@ -0,0 +1,58 @@
+let responseType = 'canMakePayment-true';
+self.addEventListener('message', event => {
+ responseType = event.data.responseType;
+});
+
+self.addEventListener('canmakepayment', event => {
+ if (event.methodData) {
+ const msg = 'Expected no method data.';
+ event.respondWith(Promise.reject(new Error(msg)));
+ return;
+ }
+
+ if (event.modifiers) {
+ const msg = 'Expected no modifiers';
+ event.respondWith(Promise.reject(new Error(msg)));
+ return;
+ }
+
+ if (event.topOrigin) {
+ const msg = `Unexpected topOrigin.`;
+ event.respondWith(Promise.reject(new Error(msg)));
+ return;
+ }
+
+ if (event.paymentRequestOrigin) {
+ const msg = `Unexpected iframe origin.`;
+ event.respondWith(Promise.reject(new Error(msg)));
+ return;
+ }
+
+ switch (responseType) {
+ case 'canMakePayment-true':
+ event.respondWith(true);
+ break;
+ case 'canMakePayment-false':
+ event.respondWith(false);
+ break;
+ case 'canMakePayment-promise-true':
+ event.respondWith(Promise.resolve(true));
+ break;
+ case 'canMakePayment-promise-false':
+ event.respondWith(Promise.resolve(false));
+ break;
+ case 'canMakePayment-custom-error':
+ event.respondWith(Promise.reject(new Error('Custom error')));
+ break;
+ default:
+ const msg = `Unrecognized payment method name "${methodName}".`;
+ event.respondWith(Promise.reject(new Error(msg)));
+ break;
+ }
+});
+
+// Respond 'true' to the 'abortpayment' event to allow tests to use abort() to
+// close an ongoing PaymentRequest.
+self.addEventListener('abortpayment', event => {
+ event.respondWith(true);
+});
diff --git a/testing/web-platform/tests/payment-handler/app-change-payment-method.js b/testing/web-platform/tests/payment-handler/app-change-payment-method.js
new file mode 100644
index 0000000000..0e5a4768e7
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/app-change-payment-method.js
@@ -0,0 +1,30 @@
+self.addEventListener('canmakepayment', (event) => {
+ event.respondWith(true);
+});
+
+async function responder(event) {
+ const methodName = event.methodData[0].supportedMethods;
+ if (!event.changePaymentMethod) {
+ return {
+ methodName,
+ details: {
+ changePaymentMethodReturned:
+ 'The changePaymentMethod() method is not implemented.',
+ },
+ };
+ }
+ let changePaymentMethodReturned;
+ try {
+ const response = await event.changePaymentMethod(methodName, {
+ country: 'US',
+ });
+ changePaymentMethodReturned = response;
+ } catch (err) {
+ changePaymentMethodReturned = err.message;
+ }
+ return {methodName, details: {changePaymentMethodReturned}};
+}
+
+self.addEventListener('paymentrequest', (event) => {
+ event.respondWith(responder(event));
+});
diff --git a/testing/web-platform/tests/payment-handler/app-change-shipping-address.js b/testing/web-platform/tests/payment-handler/app-change-shipping-address.js
new file mode 100644
index 0000000000..df39258dc9
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/app-change-shipping-address.js
@@ -0,0 +1,44 @@
+self.addEventListener('canmakepayment', (event) => {
+ event.respondWith(true);
+});
+
+async function responder(event) {
+ const methodName = event.methodData[0].supportedMethods;
+ const shippingOption = event.shippingOptions[0].id;
+ const shippingAddress = {
+ addressLine: [
+ '1875 Explorer St #1000',
+ ],
+ city: 'Reston',
+ country: 'US',
+ dependentLocality: '',
+ organization: 'Google',
+ phone: '+15555555555',
+ postalCode: '20190',
+ recipient: 'John Smith',
+ region: 'VA',
+ sortingCode: '',
+ };
+ if (!event.changeShippingAddress) {
+ return {
+ methodName,
+ details: {
+ changeShippingAddressReturned:
+ 'The changeShippingAddress() method is not implemented.',
+ },
+ };
+ }
+ let changeShippingAddressReturned;
+ try {
+ const response = await event.changeShippingAddress(shippingAddress);
+ changeShippingAddressReturned = response;
+ } catch (err) {
+ changeShippingAddressReturned = err.message;
+ }
+ return {methodName, details: {changeShippingAddressReturned}, shippingAddress,
+ shippingOption};
+}
+
+self.addEventListener('paymentrequest', (event) => {
+ event.respondWith(responder(event));
+});
diff --git a/testing/web-platform/tests/payment-handler/app-change-shipping-option.js b/testing/web-platform/tests/payment-handler/app-change-shipping-option.js
new file mode 100644
index 0000000000..ac3307b619
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/app-change-shipping-option.js
@@ -0,0 +1,44 @@
+self.addEventListener('canmakepayment', (event) => {
+ event.respondWith(true);
+});
+
+async function responder(event) {
+ const methodName = event.methodData[0].supportedMethods;
+ const shippingOption = event.shippingOptions[0].id;
+ const shippingAddress = {
+ addressLine: [
+ '1875 Explorer St #1000',
+ ],
+ city: 'Reston',
+ country: 'US',
+ dependentLocality: '',
+ organization: 'Google',
+ phone: '+15555555555',
+ postalCode: '20190',
+ recipient: 'John Smith',
+ region: 'VA',
+ sortingCode: '',
+ };
+ if (!event.changeShippingOption) {
+ return {
+ methodName,
+ details: {
+ changeShippingOptionReturned:
+ 'The changeShippingOption() method is not implemented.',
+ },
+ };
+ }
+ let changeShippingOptionReturned;
+ try {
+ const response = await event.changeShippingOption(shippingOption);
+ changeShippingOptionReturned = response;
+ } catch (err) {
+ changeShippingOptionReturned = err.message;
+ }
+ return {methodName, details: {changeShippingOptionReturned}, shippingAddress,
+ shippingOption};
+}
+
+self.addEventListener('paymentrequest', (event) => {
+ event.respondWith(responder(event));
+});
diff --git a/testing/web-platform/tests/payment-handler/app-simple.js b/testing/web-platform/tests/payment-handler/app-simple.js
new file mode 100644
index 0000000000..833a01f47e
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/app-simple.js
@@ -0,0 +1,74 @@
+self.addEventListener('paymentrequest', event => {
+ const expectedId = 'test-payment-request-identifier';
+ if (event.paymentRequestId !== expectedId) {
+ const msg = `Expected payment request identifier "${expectedId}", but got "${
+ event.paymentRequestId
+ }"`;
+ event.respondWith(Promise.reject(new Error(msg)));
+ return;
+ }
+
+ if (event.methodData.length !== 1) {
+ const msg = `Expected one method data, but got ${
+ event.methodData.length
+ } instead`;
+ event.respondWith(Promise.reject(new Error(msg)));
+ return;
+ }
+
+ const methodData = event.methodData[0];
+ const expectedMethodName = window.location.origin + '/payment-handler/payment-app/';
+ if (methodData.supportedMethods !== expectedMethodName) {
+ const msg = `Expected payment method name "${expectedMethodName}", but got "${
+ methodData.supportedMethods
+ }"`;
+ event.respondWith(Promise.reject(new Error(msg)));
+ return;
+ }
+
+ if (methodData.data.supportedNetworks) {
+ const msg =
+ 'Expected no supported networks in payment method specific data';
+ event.respondWith(Promise.reject(new Error(msg)));
+ return;
+ }
+
+ if (methodData.displayItems) {
+ const msg = 'Expected no display items';
+ event.respondWith(Promise.reject(new Error(msg)));
+ return;
+ }
+
+ const total = event.total;
+ if (!total) {
+ const msg = 'Expected total';
+ event.respondWith(Promise.reject(new Error(msg)));
+ return;
+ }
+
+ if (total.label) {
+ const msg = 'Expected no total label';
+ event.respondWith(Promise.reject(new Error(msg)));
+ return;
+ }
+
+ const expectedCurrency = 'USD';
+ if (total.currency !== expectedCurrency) {
+ const msg = `Expected currency "${expectedCurrency}", but got "${
+ total.currency
+ }"`;
+ event.respondWith(Promise.reject(new Error(msg)));
+ return;
+ }
+
+ const expectedValue = '0.01';
+ if (total.value !== expectedValue) {
+ const msg = `Expected value "${expectedValue}", but got "${total.value}"`;
+ event.respondWith(Promise.reject(new Error(msg)));
+ return;
+ }
+
+ event.respondWith({
+ methodName: expectedMethodName,
+ });
+});
diff --git a/testing/web-platform/tests/payment-handler/app-supports-shipping-contact-delegation.js b/testing/web-platform/tests/payment-handler/app-supports-shipping-contact-delegation.js
new file mode 100644
index 0000000000..770e2de64f
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/app-supports-shipping-contact-delegation.js
@@ -0,0 +1,44 @@
+self.addEventListener('canmakepayment', (event) => {
+ event.respondWith(true);
+});
+
+function responder(event) {
+ const methodName = event.methodData[0].supportedMethods;
+ const shippingOption = event.paymentOptions.requestShipping
+ ? event.shippingOptions[0].id
+ : '';
+ const payerName =
+ event.paymentOptions.requestPayerName ? 'John Smith' : '';
+ const payerEmail =
+ event.paymentOptions.requestPayerEmail ? 'smith@gmail.com' : '';
+ const payerPhone =
+ event.paymentOptions.requestPayerPhone ? '+15555555555' : '';
+ const shippingAddress = event.paymentOptions.requestShipping ? {
+ addressLine: [
+ '1875 Explorer St #1000',
+ ],
+ city: 'Reston',
+ country: 'US',
+ dependentLocality: '',
+ organization: 'Google',
+ phone: '+15555555555',
+ postalCode: '20190',
+ recipient: 'John Smith',
+ region: 'VA',
+ sortingCode: '',
+ } : {};
+
+ return {
+ methodName,
+ details: { token: '123456789'},
+ payerName,
+ payerEmail,
+ payerPhone,
+ shippingAddress,
+ shippingOption
+ };
+}
+
+self.addEventListener('paymentrequest', (event) => {
+ event.respondWith(responder(event));
+});
diff --git a/testing/web-platform/tests/payment-handler/can-make-payment-event-constructor.https.html b/testing/web-platform/tests/payment-handler/can-make-payment-event-constructor.https.html
new file mode 100644
index 0000000000..6892f01aa9
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/can-make-payment-event-constructor.https.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test for CanMakePaymentEvent Constructor</title>
+<link rel="help" href="https://w3c.github.io/payment-handler/#dom-canmakepaymentevent">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+
+test(() => {
+ assert_false('CanMakePaymentEvent' in window);
+}, 'CanMakePaymentEvent constructor must not be exposed in window');
+
+</script>
diff --git a/testing/web-platform/tests/payment-handler/can-make-payment-event-constructor.https.serviceworker.html b/testing/web-platform/tests/payment-handler/can-make-payment-event-constructor.https.serviceworker.html
new file mode 100644
index 0000000000..afff850dc4
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/can-make-payment-event-constructor.https.serviceworker.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test for CanMakePaymentEvent Constructor</title>
+<link rel="help" href="https://w3c.github.io/payment-handler/#dom-canmakepaymentevent">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+
+service_worker_test('can-make-payment-event-constructor.https.serviceworker.js',
+ 'CanMakePaymentEvent can be constructed in service worker');
+
+</script>
diff --git a/testing/web-platform/tests/payment-handler/can-make-payment-event-constructor.https.serviceworker.js b/testing/web-platform/tests/payment-handler/can-make-payment-event-constructor.https.serviceworker.js
new file mode 100644
index 0000000000..5b334d9c3a
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/can-make-payment-event-constructor.https.serviceworker.js
@@ -0,0 +1,47 @@
+importScripts('/resources/testharness.js');
+
+test(() => {
+ try {
+ new CanMakePaymentEvent('test', undefined);
+ new CanMakePaymentEvent('test', null);
+ new CanMakePaymentEvent('test', {});
+ } catch (err) {
+ assert_unreached(`Unexpected exception: ${err.message}`);
+ }
+}, 'CanMakePaymentEvent can be constructed in service worker.');
+
+test(() => {
+ const ev = new CanMakePaymentEvent('test', {
+ bubbles: true,
+ cancelable: true,
+ composed: true,
+ });
+ assert_false(ev.isTrusted, 'constructed in script, so not be trusted');
+ assert_true(ev.bubbles, 'set by EventInitDict');
+ assert_true(ev.cancelable, 'set by EventInitDict');
+ assert_true(ev.composed, 'set by EventInitDict');
+ assert_equals(ev.target, null, 'initially null');
+ assert_equals(ev.type, 'test');
+}, 'CanMakePaymentEvent can be constructed with an EventInitDict, even if not trusted');
+
+test(() => {
+ const ev = new CanMakePaymentEvent('test', {
+ topOrigin: 'https://foo.com',
+ paymentRequestOrigin: 'https://bar.com',
+ methodData: [],
+ modifiers: [],
+ });
+ assert_false(ev.isTrusted, 'constructed in script, so not be trusted');
+ assert_equals(ev.topOrigin, undefined);
+ assert_equals(ev.paymentRequestOrigin, undefined);
+ assert_equals(ev.methodData, undefined);
+ assert_equals(ev.modifiers, undefined);
+}, 'CanMakePaymentEvent can be constructed with a CanMakePaymentEventInit, even if not trusted');
+
+test(() => {
+ const ev = new CanMakePaymentEvent('test', {});
+ self.addEventListener('test', evt => {
+ assert_equals(ev, evt);
+ });
+ self.dispatchEvent(ev);
+}, 'CanMakePaymentEvent can be dispatched, even if not trusted');
diff --git a/testing/web-platform/tests/payment-handler/can-make-payment-event-constructor.https.worker.js b/testing/web-platform/tests/payment-handler/can-make-payment-event-constructor.https.worker.js
new file mode 100644
index 0000000000..d88bddceaf
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/can-make-payment-event-constructor.https.worker.js
@@ -0,0 +1,7 @@
+importScripts('/resources/testharness.js');
+
+test(() => {
+ assert_false('CanMakePaymentEvent' in self);
+}, 'CanMakePaymentEvent constructor must not be exposed in worker');
+
+done();
diff --git a/testing/web-platform/tests/payment-handler/can-make-payment-event.https.html b/testing/web-platform/tests/payment-handler/can-make-payment-event.https.html
new file mode 100644
index 0000000000..941c206e3b
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/can-make-payment-event.https.html
@@ -0,0 +1,264 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Tests for CanMakePaymentEvent</title>
+<link rel="help" href="https://w3c.github.io/payment-handler/#the-canmakepaymentevent">
+<link rel="manifest" href="manifest.json">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script>
+const instrumentKey = 'instrument-key';
+
+async function registerApp(methodName) {
+ await navigator.serviceWorker.register('app-can-make-payment.js');
+ const registration = await navigator.serviceWorker.ready;
+ if (!registration.paymentManager) {
+ return;
+ }
+ if (registration.paymentManager.requestPermission) {
+ const permission = await registration.paymentManager.requestPermission();
+ if (permission !== 'granted') {
+ return;
+ }
+ }
+ await registration.paymentManager.instruments.set(instrumentKey, {
+ name: 'Test Payment Method',
+ method: methodName,
+ });
+ return registration;
+}
+
+function buildPaymentRequest(methodName) {
+ const unsupportedMethodName = methodName + '/unsupported';
+ return new PaymentRequest(
+ [
+ {
+ supportedMethods: methodName,
+ data: {
+ defaultParameter: 'defaultValue',
+ },
+ },
+ {
+ supportedMethods: unsupportedMethodName,
+ data: {
+ defaultUnsupportedParameter: 'defaultUnsupportedValue',
+ },
+ },
+ ],
+ {
+ total: {
+ label: 'Total',
+ amount: {
+ currency: 'USD',
+ value: '0',
+ },
+ },
+ displayItems: [
+ {
+ label: 'Nada',
+ amount: {currency: 'USD', value: '0'},
+ },
+ ],
+ modifiers: [
+ {
+ supportedMethods: [methodName],
+ data: {
+ modifiedParameter: 'modifiedValue',
+ },
+ total: {
+ label: 'Modified Total',
+ amount: {
+ currency: 'USD',
+ value: '0.0001',
+ },
+ },
+ additionalDisplayItems: [
+ {
+ label: 'Something',
+ amount: {currency: 'USD', value: '0.0001'},
+ },
+ ],
+ },
+ {
+ supportedMethods: [unsupportedMethodName],
+ data: {
+ modifiedUnsupportedParameter: 'modifiedUnsupportedValue',
+ },
+ total: {
+ label: 'Modified Unsupported Total',
+ amount: {
+ currency: 'USD',
+ value: '10',
+ },
+ },
+ additionalDisplayItems: [
+ {
+ label: 'Something Unsupported',
+ amount: {currency: 'USD', value: '10'},
+ },
+ ],
+ },
+ ],
+ },
+ );
+}
+
+promise_test(async t => {
+ const methodName = window.location.origin;
+ // Intentionally do not install the payment app.
+ const request = buildPaymentRequest(methodName);
+ assert_not_equals(request, undefined);
+ let paymentRequestCanMakePaymentResult;
+ try {
+ paymentRequestCanMakePaymentResult = await request.canMakePayment();
+ } catch (err) {
+ assert_equals(
+ err.name,
+ 'NotAllowedError',
+ 'If it throws, then it must be NotAllowedError',
+ );
+ }
+ assert_false(
+ paymentRequestCanMakePaymentResult,
+ 'canMakePayment() must return false.',
+ );
+
+ await test_driver.bless('PaymentRequest.show() requires user activation');
+ await promise_rejects_dom(t, 'NotSupportedError', request.show());
+}, 'If a payment handler is not installed, then the payment method is not supported.');
+
+promise_test(async t => {
+ const methodName = window.location.origin;
+ await registerApp(methodName);
+ navigator.serviceWorker.controller.postMessage(
+ {responseType: 'canMakePayment-false'});
+ const request = buildPaymentRequest(methodName);
+ assert_not_equals(request, undefined);
+ let paymentRequestCanMakePaymentResult;
+ try {
+ paymentRequestCanMakePaymentResult = await request.canMakePayment();
+ } catch (err) {
+ assert_equals(
+ err.name,
+ 'NotAllowedError',
+ 'If it throws, then it must be NotAllowedError',
+ );
+ }
+ assert_false(
+ paymentRequestCanMakePaymentResult,
+ 'canMakePayment() must return false.',
+ );
+
+ await test_driver.bless('PaymentRequest.show() requires user activation');
+ await promise_rejects_dom(t, 'NotSupportedError', request.show());
+}, 'If CanMakePaymentEvent.respondWith(false) is called, then the payment method is not supported.');
+
+promise_test(async t => {
+ const methodName = window.location.origin;
+ await registerApp(methodName);
+ navigator.serviceWorker.controller.postMessage(
+ {responseType: 'canMakePayment-promise-false'});
+ const request = buildPaymentRequest(methodName);
+ assert_not_equals(request, undefined);
+ let paymentRequestCanMakePaymentResult;
+ try {
+ paymentRequestCanMakePaymentResult = await request.canMakePayment();
+ } catch (err) {
+ assert_equals(
+ err.name,
+ 'NotAllowedError',
+ 'If it throws, then it must be NotAllowedError',
+ );
+ }
+ assert_false(
+ paymentRequestCanMakePaymentResult,
+ 'canMakePayment() must return false.',
+ );
+
+ await test_driver.bless('PaymentRequest.show() requires user activation');
+ await promise_rejects_dom(t, 'NotSupportedError', request.show());
+}, 'If CanMakePaymentEvent.respondWith(Promise.resolve(false)) is called, then the payment method is not supported.');
+
+promise_test(async t => {
+ const methodName = window.location.origin;
+ await registerApp(methodName);
+ navigator.serviceWorker.controller.postMessage(
+ {responseType: 'canMakePayment-true'});
+ const request = buildPaymentRequest(methodName);
+ assert_not_equals(request, undefined);
+ let paymentRequestCanMakePaymentResult;
+ try {
+ paymentRequestCanMakePaymentResult = await request.canMakePayment();
+ } catch (err) {
+ assert_equals(
+ err.name,
+ 'NotAllowedError',
+ 'If it throws, then it must be NotAllowedError',
+ );
+ }
+ assert_true(
+ paymentRequestCanMakePaymentResult,
+ 'canMakePayment() must return true.',
+ );
+
+ await test_driver.bless('PaymentRequest.show() requires user activation');
+ const acceptPromise = request.show();
+ await request.abort();
+ await promise_rejects_dom(t, 'AbortError', acceptPromise);
+}, 'If CanMakePaymentEvent.respondWith(true) is called, then the payment method is supported.');
+
+promise_test(async t => {
+ const methodName = window.location.origin;
+ await registerApp(methodName);
+ navigator.serviceWorker.controller.postMessage(
+ {responseType: 'canMakePayment-promise-true'});
+ const request = buildPaymentRequest(methodName);
+ assert_not_equals(request, undefined);
+ let paymentRequestCanMakePaymentResult;
+ try {
+ paymentRequestCanMakePaymentResult = await request.canMakePayment();
+ } catch (err) {
+ assert_equals(
+ err.name,
+ 'NotAllowedError',
+ 'If it throws, then it must be NotAllowedError',
+ );
+ }
+ assert_true(
+ paymentRequestCanMakePaymentResult,
+ 'canMakePayment() must return true.',
+ );
+
+ await test_driver.bless('PaymentRequest.show() requires user activation');
+ const acceptPromise = request.show();
+ await request.abort();
+ await promise_rejects_dom(t, 'AbortError', acceptPromise);
+}, 'If CanMakePaymentEvent.respondWith(Promise.resolve(true)) is called, then the payment method is supported.');
+
+promise_test(async t => {
+ const methodName = window.location.origin;
+ await registerApp(methodName);
+ navigator.serviceWorker.controller.postMessage(
+ {responseType: 'canMakePayment-custom-error'});
+ const request = buildPaymentRequest(methodName);
+ assert_not_equals(request, undefined);
+ let paymentRequestCanMakePaymentResult;
+ try {
+ paymentRequestCanMakePaymentResult = await request.canMakePayment();
+ } catch (err) {
+ assert_equals(
+ err.name,
+ 'NotAllowedError',
+ 'If it throws, then it must be NotAllowedError',
+ );
+ }
+ assert_false(
+ paymentRequestCanMakePaymentResult,
+ 'canMakePayment() must return false.',
+ );
+
+ await test_driver.bless('PaymentRequest.show() requires user activation');
+ await promise_rejects_dom(t, 'NotSupportedError', request.show());
+}, 'If CanMakePaymentEvent.respondWith(Promise.reject(error)) is called, then the payment method is not supported.');
+</script>
diff --git a/testing/web-platform/tests/payment-handler/change-payment-method-manual.https.html b/testing/web-platform/tests/payment-handler/change-payment-method-manual.https.html
new file mode 100644
index 0000000000..1640420c62
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/change-payment-method-manual.https.html
@@ -0,0 +1,170 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Tests for PaymentRequestEvent.changePaymentMethod()</title>
+<link
+ rel="help"
+ href="https://w3c.github.io/payment-handler/#changepaymentmethod-method"
+/>
+<link rel="manifest" href="/payment-handler/manifest.json" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="register-and-activate-service-worker.js"></script>
+<p>If the payment sheet is shown, please authorize the mock payment.</p>
+<script>
+ async function runTests(registration) {
+ const methodName = window.location.origin + '/payment-handler/payment-app/';
+ await registration.paymentManager.instruments.clear();
+ await registration.paymentManager.instruments.set('instrument-key', {
+ name: 'Instrument Name',
+ method: methodName,
+ });
+
+ promise_test(async (t) => {
+ const request = new PaymentRequest([{supportedMethods: methodName}], {
+ total: {label: 'Total', amount: {currency: 'USD', value: '0.01'}},
+ });
+ // Intentionally do not respond to the 'paymentmethodchange' event.
+ const response = await test_driver.bless('showing a payment sheet', () =>
+ request.show()
+ );
+ const complete_promise = response.complete('success');
+
+ assert_equals(response.details.changePaymentMethodReturned, null);
+
+ return complete_promise;
+ }, 'If updateWith(details) is not run, changePaymentMethod() returns null.');
+
+ promise_test(async (t) => {
+ const request = new PaymentRequest([{supportedMethods: methodName}], {
+ total: {label: 'Total', amount: {currency: 'USD', value: '0.01'}},
+ });
+ request.addEventListener('paymentmethodchange', (event) => {
+ assert_equals(event.methodName, methodName);
+ assert_equals(event.methodDetails.country, 'US');
+ event.updateWith(Promise.reject('Error'));
+ });
+ const response_promise = test_driver.bless(
+ 'showing a payment sheet',
+ () => request.show()
+ );
+
+ return promise_rejects_dom(t, 'AbortError', response_promise);
+ }, 'If updateWith(details) is rejected, abort the PaymentRequest.show().');
+
+ promise_test(async (t) => {
+ const request = new PaymentRequest([{supportedMethods: methodName}], {
+ total: {label: 'Total', amount: {currency: 'USD', value: '0.01'}},
+ });
+ request.addEventListener('paymentmethodchange', (event) => {
+ assert_equals(event.methodName, methodName);
+ assert_equals(event.methodDetails.country, 'US');
+ event.updateWith(
+ new Promise(() => {
+ throw 'Error for test';
+ })
+ );
+ });
+ const response_promise = test_driver.bless(
+ 'showing a payment sheet',
+ () => request.show()
+ );
+
+ return promise_rejects_dom(t, 'AbortError', response_promise);
+ }, 'If updateWith(details) throws inside of the promise, abort the PaymentRequest.show().');
+
+ promise_test(async (t) => {
+ const request = new PaymentRequest([{supportedMethods: methodName}], {
+ total: {label: 'Total', amount: {currency: 'USD', value: '0.01'}},
+ });
+ request.addEventListener('paymentmethodchange', (event) => {
+ assert_equals(event.methodName, methodName);
+ assert_equals(event.methodDetails.country, 'US');
+ event.updateWith({
+ total: {label: 'Total', amount: {currency: 'GBP', value: '0.02'}},
+ error: 'Error for test',
+ modifiers: [
+ {
+ supportedMethods: methodName,
+ data: {soup: 'potato'},
+ total: {
+ label: 'Modified total',
+ amount: {currency: 'EUR', value: '0.03'},
+ },
+ additionalDisplayItems: [
+ {
+ label: 'Modified display item',
+ amount: {currency: 'INR', value: '0.06'},
+ },
+ ],
+ },
+ {
+ supportedMethods: methodName + '2',
+ data: {soup: 'tomato'},
+ total: {
+ label: 'Modified total #2',
+ amount: {currency: 'CHF', value: '0.07'},
+ },
+ additionalDisplayItems: [
+ {
+ label: 'Modified display item #2',
+ amount: {currency: 'CAD', value: '0.08'},
+ },
+ ],
+ },
+ ],
+ paymentMethodErrors: {country: 'Unsupported country'},
+ displayItems: [
+ {
+ label: 'Display item',
+ amount: {currency: 'CNY', value: '0.04'},
+ },
+ ],
+ shippingOptions: [
+ {
+ label: 'Shipping option',
+ id: 'id',
+ amount: {currency: 'JPY', value: '0.05'},
+ },
+ ],
+ });
+ });
+ const response = await test_driver.bless('showing a payment sheet', () =>
+ request.show()
+ );
+ const complete_promise = response.complete('success');
+ const changePaymentMethodReturned =
+ response.details.changePaymentMethodReturned;
+
+ assert_equals(changePaymentMethodReturned.total.currency, 'GBP');
+ assert_equals(changePaymentMethodReturned.total.value, '0.02');
+ assert_equals(changePaymentMethodReturned.total.label, undefined);
+ assert_equals(changePaymentMethodReturned.error, 'Error for test');
+ assert_equals(changePaymentMethodReturned.modifiers.length, 1);
+ assert_equals(changePaymentMethodReturned.displayItems, undefined);
+ assert_equals(changePaymentMethodReturned.shippingOptions, undefined);
+ assert_equals(
+ changePaymentMethodReturned.paymentMethodErrors.country,
+ 'Unsupported country'
+ );
+
+ const modifier = changePaymentMethodReturned.modifiers[0];
+
+ assert_equals(modifier.supportedMethods, methodName);
+ assert_equals(modifier.data.soup, 'potato');
+ assert_equals(modifier.total.label, '');
+ assert_equals(modifier.total.amount.currency, 'EUR');
+ assert_equals(modifier.total.amount.value, '0.03');
+ assert_equals(modifier.additionalDisplayItems, undefined);
+
+ return complete_promise;
+ }, 'The changePaymentMethod() returns some details from the "paymentmethodchange" event\'s updateWith(details) call.');
+ }
+
+ registerAndActiveServiceWorker(
+ 'app-change-payment-method.js',
+ 'payment-app/',
+ runTests
+ );
+</script>
diff --git a/testing/web-platform/tests/payment-handler/change-shipping-address-manual.https.html b/testing/web-platform/tests/payment-handler/change-shipping-address-manual.https.html
new file mode 100644
index 0000000000..3b98d56a25
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/change-shipping-address-manual.https.html
@@ -0,0 +1,161 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Tests for PaymentRequestEvent.changeShippingAddress()</title>
+
+<link rel="manifest" href="/payment-handler/basic-card.json" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="register-and-activate-service-worker.js"></script>
+<p>If the payment sheet is shown, please authorize the mock payment.</p>
+<script>
+ const methodName = window.location.origin + '/payment-handler/payment-app/';
+ function createRequest() {
+ return new PaymentRequest([{supportedMethods: methodName}], {
+ total: {label: 'Total', amount: {currency: 'USD', value: '0.01'}},
+ shippingOptions: [{
+ id: 'freeShippingOption',
+ label: 'Free global shipping',
+ amount: {
+ currency: 'USD',
+ value: '0',
+ },
+ selected: false,
+ }],
+ }, {requestShipping: true});
+ }
+
+ async function completeAppSetUp(registration) {
+ await registration.paymentManager.instruments.clear();
+ await registration.paymentManager.instruments.set('instrument-key', {
+ name: 'Instrument Name',
+ method: methodName,
+ });
+ await navigator.serviceWorker.ready;
+ await registration.paymentManager.enableDelegations(['shippingAddress']);
+ }
+
+ async function runTests(registration) {
+ await completeAppSetUp(registration);
+ promise_test(async (t) => {
+ const request = createRequest();
+ // Intentionally do not respond to the 'shippingaddresschange' event.
+ const response = await test_driver.bless('showing a payment sheet', () =>
+ request.show()
+ );
+ const complete_promise = response.complete('success');
+
+ assert_equals(response.details.changeShippingAddressReturned, null);
+
+ return complete_promise;
+ }, 'If updateWith(details) is not run, changeShippingAddress() returns null.');
+
+ promise_test(async (t) => {
+ const request = createRequest();
+ request.addEventListener('shippingaddresschange', (event) => {
+ assert_equals(request.shippingAddress.organization, '', 'organization should be redacted');
+ assert_equals(request.shippingAddress.phone, '', 'phone should be redacted');
+ assert_equals(request.shippingAddress.recipient, '', 'recipient should be redacted');
+ assert_equals(request.shippingAddress.addressLine.length, 0, 'addressLine should be redacted');
+ assert_equals(request.shippingAddress.city, 'Reston');
+ assert_equals(request.shippingAddress.country, 'US');
+ assert_equals(request.shippingAddress.postalCode, '20190');
+ assert_equals(request.shippingAddress.region, 'VA');
+ event.updateWith({
+ total: {label: 'Total', amount: {currency: 'GBP', value: '0.02'}},
+ error: 'Error for test',
+ modifiers: [
+ {
+ supportedMethods: methodName,
+ data: {soup: 'potato'},
+ total: {
+ label: 'Modified total',
+ amount: {currency: 'EUR', value: '0.03'},
+ },
+ additionalDisplayItems: [
+ {
+ label: 'Modified display item',
+ amount: {currency: 'INR', value: '0.06'},
+ },
+ ],
+ },
+ {
+ supportedMethods: methodName + '2',
+ data: {soup: 'tomato'},
+ total: {
+ label: 'Modified total #2',
+ amount: {currency: 'CHF', value: '0.07'},
+ },
+ additionalDisplayItems: [
+ {
+ label: 'Modified display item #2',
+ amount: {currency: 'CAD', value: '0.08'},
+ },
+ ],
+ },
+ ],
+ displayItems: [
+ {
+ label: 'Display item',
+ amount: {currency: 'CNY', value: '0.04'},
+ },
+ ],
+ shippingOptions: [
+ {
+ id: 'freeShippingOption',
+ label: 'express global shipping',
+ amount: {
+ currency: 'USD',
+ value: '0',
+ },
+ selected: true,
+ }
+ ],
+ shippingAddressErrors: {
+ country: 'US only shipping',
+ }
+ });
+ });
+ const response = await test_driver.bless('showing a payment sheet', () =>
+ request.show()
+ );
+ const complete_promise = response.complete('success');
+ const changeShippingAddressReturned =
+ response.details.changeShippingAddressReturned;
+
+ assert_equals(changeShippingAddressReturned.total.currency, 'GBP');
+ assert_equals(changeShippingAddressReturned.total.value, '0.02');
+ assert_equals(changeShippingAddressReturned.total.label, undefined);
+ assert_equals(changeShippingAddressReturned.error, 'Error for test');
+ assert_equals(changeShippingAddressReturned.modifiers.length, 1);
+ assert_equals(changeShippingAddressReturned.displayItems, undefined);
+ assert_equals(changeShippingAddressReturned.shippingOptions.length, 1);
+ assert_equals(changeShippingAddressReturned.paymentMethodErrors, undefined);
+ assert_equals(changeShippingAddressReturned.shippingAddressErrors.country, 'US only shipping');
+
+ const shipping_option = changeShippingAddressReturned.shippingOptions[0];
+ assert_equals(shipping_option.id, 'freeShippingOption' );
+ assert_equals(shipping_option.label, 'express global shipping');
+ assert_equals(shipping_option.amount.currency, 'USD');
+ assert_equals(shipping_option.amount.value, '0');
+ assert_true(shipping_option.selected);
+
+ const modifier = changeShippingAddressReturned.modifiers[0];
+ assert_equals(modifier.supportedMethods, methodName);
+ assert_equals(modifier.data.soup, 'potato');
+ assert_equals(modifier.total.label, '');
+ assert_equals(modifier.total.amount.currency, 'EUR');
+ assert_equals(modifier.total.amount.value, '0.03');
+ assert_equals(modifier.additionalDisplayItems, undefined);
+
+ return complete_promise;
+ }, 'The changeShippingAddress() returns some details from the "shippingaddresschange" event\'s updateWith(details) call.');
+ }
+
+ registerAndActiveServiceWorker(
+ 'app-change-shipping-address.js',
+ 'payment-app/',
+ runTests
+ );
+</script>
diff --git a/testing/web-platform/tests/payment-handler/change-shipping-option-manual.https.html b/testing/web-platform/tests/payment-handler/change-shipping-option-manual.https.html
new file mode 100644
index 0000000000..2511fc5ea0
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/change-shipping-option-manual.https.html
@@ -0,0 +1,160 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Tests for PaymentRequestEvent.changeShippingOption()</title>
+
+<link rel="manifest" href="/payment-handler/manifest.json" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="register-and-activate-service-worker.js"></script>
+<p>If the payment sheet is shown, please authorize the mock payment.</p>
+<script>
+ const methodName = window.location.origin + '/payment-handler/payment-app/';
+ function createRequest() {
+ return new PaymentRequest([{supportedMethods: methodName}], {
+ total: {label: 'Total', amount: {currency: 'USD', value: '0.01'}},
+ shippingOptions: [{
+ id: 'freeShippingOption',
+ label: 'Free global shipping',
+ amount: {
+ currency: 'USD',
+ value: '0',
+ },
+ selected: false,
+ },
+ {
+ id: 'expressShippingOption',
+ label: 'express global shipping',
+ amount: {
+ currency: 'USD',
+ value: '0',
+ },
+ selected: true,
+ }],
+ }, {requestShipping: true});
+ }
+
+ async function completeAppSetUp(registration) {
+ await registration.paymentManager.instruments.clear();
+ await registration.paymentManager.instruments.set('instrument-key', {
+ name: 'Instrument Name',
+ method: methodName,
+ });
+ await navigator.serviceWorker.ready;
+ await registration.paymentManager.enableDelegations(['shippingAddress']);
+ }
+
+ async function runTests(registration) {
+ await completeAppSetUp(registration);
+ promise_test(async (t) => {
+ const request = createRequest();
+ // Intentionally do not respond to the 'shippingoptionchange' event.
+ const response = await test_driver.bless('showing a payment sheet', () =>
+ request.show()
+ );
+ const complete_promise = response.complete('success');
+
+ assert_equals(response.details.changeShippingOptionReturned, null);
+
+ return complete_promise;
+ }, 'If updateWith(details) is not run, changeShippingOption() returns null.');
+
+ promise_test(async (t) => {
+ const request = createRequest();
+ request.addEventListener('shippingoptionchange', (event) => {
+ assert_equals(request.shippingOption, 'freeShippingOption');
+ event.updateWith({
+ total: {label: 'Total', amount: {currency: 'GBP', value: '0.02'}},
+ error: 'Error for test',
+ modifiers: [
+ {
+ supportedMethods: methodName,
+ data: {soup: 'potato'},
+ total: {
+ label: 'Modified total',
+ amount: {currency: 'EUR', value: '0.03'},
+ },
+ additionalDisplayItems: [
+ {
+ label: 'Modified display item',
+ amount: {currency: 'INR', value: '0.06'},
+ },
+ ],
+ },
+ {
+ supportedMethods: methodName + '2',
+ data: {soup: 'tomato'},
+ total: {
+ label: 'Modified total #2',
+ amount: {currency: 'CHF', value: '0.07'},
+ },
+ additionalDisplayItems: [
+ {
+ label: 'Modified display item #2',
+ amount: {currency: 'CAD', value: '0.08'},
+ },
+ ],
+ },
+ ],
+ displayItems: [
+ {
+ label: 'Display item',
+ amount: {currency: 'CNY', value: '0.04'},
+ },
+ ],
+ shippingOptions: [
+ {
+ id: 'freeShippingOption',
+ label: 'express global shipping',
+ amount: {
+ currency: 'USD',
+ value: '0',
+ },
+ selected: true,
+ }
+ ],
+ });
+ });
+ const response = await test_driver.bless('showing a payment sheet', () =>
+ request.show()
+ );
+ const complete_promise = response.complete('success');
+ const changeShippingOptionReturned =
+ response.details.changeShippingOptionReturned;
+
+ assert_equals(changeShippingOptionReturned.total.currency, 'GBP');
+ assert_equals(changeShippingOptionReturned.total.value, '0.02');
+ assert_equals(changeShippingOptionReturned.total.label, undefined);
+ assert_equals(changeShippingOptionReturned.error, 'Error for test');
+ assert_equals(changeShippingOptionReturned.modifiers.length, 1);
+ assert_equals(changeShippingOptionReturned.displayItems, undefined);
+ assert_equals(changeShippingOptionReturned.shippingOptions.length, 1);
+ assert_equals(changeShippingOptionReturned.paymentMethodErrors, undefined);
+ assert_equals(changeShippingOptionReturned.shippingAddressErrors, undefined);
+
+ const shipping_option = changeShippingOptionReturned.shippingOptions[0];
+ assert_equals(shipping_option.id, 'freeShippingOption' );
+ assert_equals(shipping_option.label, 'express global shipping');
+ assert_equals(shipping_option.amount.currency, 'USD');
+ assert_equals(shipping_option.amount.value, '0');
+ assert_true(shipping_option.selected);
+
+ const modifier = changeShippingOptionReturned.modifiers[0];
+ assert_equals(modifier.supportedMethods, methodName);
+ assert_equals(modifier.data.soup, 'potato');
+ assert_equals(modifier.total.label, '');
+ assert_equals(modifier.total.amount.currency, 'EUR');
+ assert_equals(modifier.total.amount.value, '0.03');
+ assert_equals(modifier.additionalDisplayItems, undefined);
+
+ return complete_promise;
+ }, 'The changeShippingOption() returns some details from the "shippingoptionchange" event\'s updateWith(details) call.');
+ }
+
+ registerAndActiveServiceWorker(
+ 'app-change-shipping-option.js',
+ 'payment-app/',
+ runTests
+ );
+</script>
diff --git a/testing/web-platform/tests/payment-handler/idlharness.https.any.js b/testing/web-platform/tests/payment-handler/idlharness.https.any.js
new file mode 100644
index 0000000000..dfb0190aba
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/idlharness.https.any.js
@@ -0,0 +1,47 @@
+// META: global=window,worker
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+// META: script=/service-workers/service-worker/resources/test-helpers.sub.js
+// META: timeout=long
+
+'use strict';
+
+// https://w3c.github.io/payment-handler/
+
+idl_test(
+ ['payment-handler'],
+ ['service-workers', 'html', 'dom'],
+ async (idl_array, t) => {
+ const isWindow = self.GLOBAL.isWindow();
+ const isServiceWorker = 'ServiceWorkerGlobalScope' in self;
+ const hasRegistration = isServiceWorker || isWindow;
+
+ if (hasRegistration) {
+ idl_array.add_objects({
+ ServiceWorkerRegistration: ['registration'],
+ PaymentManager: ['paymentManager'],
+ PaymentInstruments: ['instruments'],
+ });
+ }
+ if (isServiceWorker) {
+ idl_array.add_objects({
+ ServiceWorkerGlobalScope: ['self'],
+ CanMakePaymentEvent: ['new CanMakePaymentEvent("type")'],
+ PaymentRequestEvent: ['new PaymentRequestEvent("type")'],
+ })
+ }
+
+ if (isWindow) {
+ const scope = '/service-workers/service-worker/resources/';
+ const reg = await service_worker_unregister_and_register(
+ t, '/service-workers/service-worker/resources/empty-worker.js', scope);
+ self.registration = reg;
+ await wait_for_state(t, reg.installing, "activated");
+ add_completion_callback(() => reg.unregister());
+ }
+ if (hasRegistration) {
+ self.paymentManager = self.registration.paymentManager;
+ self.instruments = self.paymentManager.instruments;
+ }
+ }
+);
diff --git a/testing/web-platform/tests/payment-handler/manifest.json b/testing/web-platform/tests/payment-handler/manifest.json
new file mode 100644
index 0000000000..875d74b663
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/manifest.json
@@ -0,0 +1,10 @@
+{
+ "name": "Test Payment Handler",
+ "icons": [
+ {
+ "src": "/images/rgrg-256x256.png",
+ "sizes": "256x256",
+ "type": "image/png"
+ }
+ ]
+}
diff --git a/testing/web-platform/tests/payment-handler/payment-app/payment.html b/testing/web-platform/tests/payment-handler/payment-app/payment.html
new file mode 100644
index 0000000000..37d2452ed9
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/payment-app/payment.html
@@ -0,0 +1,5 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test Payment App</title>
+<p>Account balance: $10.00</p>
+<button>Authorize</button>
diff --git a/testing/web-platform/tests/payment-handler/payment-instruments.https.html b/testing/web-platform/tests/payment-handler/payment-instruments.https.html
new file mode 100644
index 0000000000..121c131568
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/payment-instruments.https.html
@@ -0,0 +1,379 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Tests for PaymentInstruments interface</title>
+<link rel="help" href="https://w3c.github.io/payment-handler/#paymentinstruments-interface">
+<link rel="manifest" href="manifest.json">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="register-and-activate-service-worker.js"></script>
+<script>
+function runTests(registration) {
+ const methodName = window.location.origin + '/payment-handler/payment-app/';
+
+ promise_test(async t => {
+ await registration.paymentManager.instruments.clear();
+ await registration.paymentManager.instruments.set('instrument-key-1', {
+ name: 'Instrument Name 1',
+ });
+ await registration.paymentManager.instruments.set('instrument-key-2', {
+ name: 'Instrument Name 2',
+ });
+ await registration.paymentManager.instruments.delete('instrument-key-1');
+ await registration.paymentManager.instruments.set('instrument-key-1', {
+ name: 'Instrument Name 1',
+ });
+ const keys = await registration.paymentManager.instruments.keys();
+ assert_array_equals(keys, ['instrument-key-2', 'instrument-key-1']);
+ }, 'Instrument keys are returned in the original insertion order');
+
+ promise_test(async t => {
+ await registration.paymentManager.instruments.clear();
+ await registration.paymentManager.instruments.set(
+ 'existing-instrument-key',
+ {
+ name: 'Instrument Name',
+ },
+ );
+ const result = await registration.paymentManager.instruments.delete(
+ 'existing-instrument-key',
+ );
+ assert_true(result);
+ }, 'Deleting an existing instrument returns true');
+
+ promise_test(async t => {
+ await registration.paymentManager.instruments.clear();
+ await registration.paymentManager.instruments.set(
+ 'existing-instrument-key',
+ {
+ name: 'Instrument Name',
+ },
+ );
+ await registration.paymentManager.instruments.delete(
+ 'existing-instrument-key',
+ );
+ const result = await registration.paymentManager.instruments.delete(
+ 'existing-instrument-key',
+ );
+ assert_false(result);
+ }, 'Deleting an existing instrument the second time returns false');
+
+ promise_test(async t => {
+ await registration.paymentManager.instruments.clear();
+ const result = await registration.paymentManager.instruments.delete(
+ 'non-existing-instrument-key',
+ );
+ assert_false(result);
+ }, 'Deleting a non-existing instrument returns false');
+
+ promise_test(async t => {
+ await registration.paymentManager.instruments.clear();
+ await registration.paymentManager.instruments.set(
+ 'existing-instrument-key',
+ {
+ name: 'Instrument Name',
+ icons: [
+ {
+ src: '/images/rgrg-256x256.png',
+ sizes: '256x256',
+ type: 'image/png',
+ },
+ ],
+ method: methodName,
+ },
+ );
+ const result = await registration.paymentManager.instruments.get(
+ 'existing-instrument-key',
+ );
+ assert_equals(result.name, 'Instrument Name');
+ }, 'Getting an existing instrument returns the instrument');
+
+ promise_test(async t => {
+ await registration.paymentManager.instruments.clear();
+ const result = await registration.paymentManager.instruments.get(
+ 'non-existing-instrument-key',
+ );
+ assert_equals(result, undefined);
+ }, 'Getting a non-existing instrument returns undefined');
+
+ promise_test(async t => {
+ await registration.paymentManager.instruments.clear();
+ await registration.paymentManager.instruments.set(
+ 'existing-instrument-key',
+ {
+ name: 'Instrument Name v1',
+ icons: [
+ {src: '/images/green-16x16.png', sizes: '16x16', type: 'image/png'},
+ ],
+ method: methodName,
+ },
+ );
+ let result = await registration.paymentManager.instruments.get(
+ 'existing-instrument-key',
+ );
+ assert_equals(result.name, 'Instrument Name v1');
+ assert_equals(result.icons.length, 1);
+ assert_equals(
+ result.icons[0].src,
+ new URL('/images/green-16x16.png', window.location.href).href,
+ );
+ assert_equals(result.icons[0].sizes, '16x16');
+ assert_equals(result.icons[0].type, 'image/png');
+ assert_equals(result.method, methodName);
+ assert_array_equals(result.capabilities.supportedNetworks, ['mir']);
+ await registration.paymentManager.instruments.set(
+ 'existing-instrument-key',
+ {
+ name: 'Instrument Name v2',
+ icons: [
+ {
+ src: '/images/rgrg-256x256.png',
+ sizes: '256x256',
+ type: 'image/png',
+ },
+ ],
+ method: methodName,
+ },
+ );
+ result = await registration.paymentManager.instruments.get(
+ 'existing-instrument-key',
+ );
+ assert_equals(result.name, 'Instrument Name v2');
+ assert_equals(result.icons.length, 1);
+ assert_equals(
+ result.icons[0].src,
+ new URL('/images/rgrg-256x256.png', window.location.href).href,
+ );
+ assert_equals(result.icons[0].sizes, '256x256');
+ assert_equals(result.icons[0].type, 'image/png');
+ assert_equals(result.method, methodName);
+ }, 'Resetting an existing instrument updates the instrument');
+
+ promise_test(async t => {
+ await registration.paymentManager.instruments.clear();
+ await registration.paymentManager.instruments.set(
+ 'existing-instrument-key',
+ {
+ name: 'Instrument Name',
+ icons: [
+ {
+ src: '/images/rgrg-256x256.png',
+ sizes: '256x256',
+ type: 'image/png',
+ },
+ ],
+ method: methodName,
+ },
+ );
+ await registration.paymentManager.instruments.clear();
+ const result = await registration.paymentManager.instruments.get(
+ 'existing-instrument-key',
+ );
+ assert_equals(result, undefined);
+ }, 'Clearing the instruments');
+
+ promise_test(async t => {
+ await registration.paymentManager.instruments.clear();
+ const setPromise = registration.paymentManager.instruments.set(
+ 'instrument-key',
+ {
+ name: 'Instrument Name',
+ icons: [
+ {
+ src: '/images/rgrg-256x256.png',
+ sizes: '256x256',
+ type: 'image/jif',
+ },
+ ],
+ method: methodName,
+ },
+ );
+ return promise_rejects_js(t, TypeError, setPromise);
+ }, 'Cannot register instruments with invalid icon media type image/jif');
+
+ promise_test(async t => {
+ await registration.paymentManager.instruments.clear();
+ const setPromise = registration.paymentManager.instruments.set(
+ 'instrument-key',
+ {
+ name: 'Instrument Name',
+ icons: [
+ {
+ src: '/images/rgrg-256x256.png',
+ sizes: '256x256',
+ type: 'image/pn' + 'g'.repeat(100000),
+ },
+ ],
+ method: methodName,
+ },
+ );
+ return promise_rejects_js(t, TypeError, setPromise);
+ }, "Don't crash when registering instruments with very long icon media type image/pngggggg...");
+
+ promise_test(async t => {
+ await registration.paymentManager.instruments.clear();
+ return registration.paymentManager.instruments.set('instrument-key', {
+ name: 'Instrument Name',
+ icons: [
+ {
+ src: '/images/rgrg-256x256.png',
+ sizes: '8'.repeat(100000) + 'x' + '8'.repeat(100000),
+ type: 'image/png',
+ },
+ ],
+ method: methodName,
+ });
+ }, "Don't crash when registering an instrument with a very long icon size 888...x888...");
+
+ promise_test(async t => {
+ await registration.paymentManager.instruments.clear();
+ return registration.paymentManager.instruments.set('instrument-key', {
+ name: 'Instrument Name',
+ icons: [
+ {
+ src: '/images/rgrg-256x256.png',
+ type: 'image/png',
+ },
+ ],
+ method: methodName,
+ });
+ }, "Don't crash when 'sizes' missing from icon definition");
+
+ promise_test(async t => {
+ await registration.paymentManager.instruments.clear();
+ return registration.paymentManager.instruments.set('instrument-key', {
+ name: 'Instrument Name',
+ icons: [
+ {
+ src: '/images/rgrg-256x256.png',
+ sizes: '256x256',
+ },
+ ],
+ method: methodName,
+ });
+ }, "Don't crash when 'type' missing from icon definition");
+
+ promise_test(async t => {
+ await registration.paymentManager.instruments.clear();
+ const setPromise = registration.paymentManager.instruments.set(
+ 'instrument-key',
+ {
+ name: 'Instrument Name',
+ icons: [
+ {
+ src: '/images/rgrg-256x256.png',
+ sizes: '256 256',
+ type: 'image/png',
+ },
+ ],
+ method: methodName,
+ },
+ );
+ return promise_rejects_js(t, TypeError, setPromise);
+ }, 'Cannot register instruments with invalid icon size "256 256" (missing "x")');
+
+ promise_test(async t => {
+ await registration.paymentManager.instruments.clear();
+ const setPromise = registration.paymentManager.instruments.set(
+ 'instrument-key',
+ {
+ name: 'Instrument Name',
+ icons: [
+ {
+ src: '/images/rg\0rg-256x256.png',
+ sizes: '256x256',
+ type: 'image/png',
+ },
+ ],
+ method: methodName,
+ },
+ );
+ return promise_rejects_js(t, TypeError, setPromise);
+ }, 'Cannot register instruments with invalid icon URL (has a null character)');
+
+ promise_test(async t => {
+ await registration.paymentManager.instruments.clear();
+ const setPromise = registration.paymentManager.instruments.set(
+ 'instrument-key',
+ {
+ name: 'Instrument Name',
+ icons: [
+ {
+ src: 'http://test.example/images/rgrg-256x256.png',
+ sizes: '256x256',
+ type: 'image/png',
+ },
+ ],
+ method: methodName,
+ },
+ );
+ return promise_rejects_js(t, TypeError, setPromise);
+ }, 'Cannot register instruments with non-existing non-https icon URL');
+
+ promise_test(async t => {
+ await registration.paymentManager.instruments.clear();
+ const setPromise = registration.paymentManager.instruments.set(
+ 'instrument-key',
+ {
+ name: 'Instrument Name',
+ icons: [
+ {
+ src:
+ 'http://www.chromium.org/_/rsrc/1438879449147/config/customLogo.gif',
+ sizes: '48x48',
+ type: 'image/gif',
+ },
+ ],
+ method: methodName,
+ },
+ );
+ return promise_rejects_js(t, TypeError, setPromise);
+ }, 'Cannot register instruments with an existing non-https icon URL');
+
+ async function testUnusualStrings(existingKey, nonExistingKey) {
+ await registration.paymentManager.instruments.clear();
+ await registration.paymentManager.instruments.set(existingKey, {
+ name: existingKey,
+ icons: [
+ {src: '/images/rgrg-256x256.png', sizes: '256x256', type: 'image/png'},
+ ],
+ method: existingKey,
+ capabilities: {aCapabilityName: existingKey},
+ });
+ const hasExistingInstrument = await registration.paymentManager.instruments.has(
+ existingKey,
+ );
+ assert_true(hasExistingInstrument);
+ const hasNonExistingInstrument = await registration.paymentManager.instruments.has(
+ nonExistingKey,
+ );
+ assert_false(hasNonExistingInstrument);
+ const existingInstrument = await registration.paymentManager.instruments.get(
+ existingKey,
+ );
+ assert_equals(existingInstrument.name, existingKey);
+ const nonExistingInstrument = await registration.paymentManager.instruments.get(
+ nonExistingKey,
+ );
+ assert_equals(nonExistingInstrument, undefined);
+ const deletedExistingInstrument = await registration.paymentManager.instruments.delete(
+ existingKey,
+ );
+ assert_true(deletedExistingInstrument);
+ const deletedNonExistingInstrument = await registration.paymentManager.instruments.delete(
+ nonExistingKey,
+ );
+ assert_false(deletedNonExistingInstrument);
+ }
+
+ promise_test(async t => {
+ const length = 100000;
+ await testUnusualStrings('0'.repeat(length), '1'.repeat(length));
+ }, "Don't crash on very long key, name, method, and capability strings.");
+
+ promise_test(async t => {
+ await testUnusualStrings('foo\0bar', 'foo\0baz');
+ }, "Don't crash on null characters in key, name, method, and capability strings.");
+}
+
+registerAndActiveServiceWorker('app-simple.js', 'payment-app/', runTests);
+</script>
diff --git a/testing/web-platform/tests/payment-handler/payment-request-event-constructor.https.html b/testing/web-platform/tests/payment-handler/payment-request-event-constructor.https.html
new file mode 100644
index 0000000000..31ac8cafa7
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/payment-request-event-constructor.https.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test for PaymentRequestEvent Constructor</title>
+<link rel="help" href="https://w3c.github.io/payment-handler/#the-paymentrequestevent">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+
+test(() => {
+ assert_false('PaymentRequestEvent' in window);
+}, 'PaymentRequestEvent constructor must not be exposed in window');
+
+</script>
diff --git a/testing/web-platform/tests/payment-handler/payment-request-event-constructor.https.serviceworker.html b/testing/web-platform/tests/payment-handler/payment-request-event-constructor.https.serviceworker.html
new file mode 100644
index 0000000000..a64c4290e0
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/payment-request-event-constructor.https.serviceworker.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test for PaymentRequestEvent Constructor</title>
+<link rel="help" href="https://w3c.github.io/payment-handler/#the-paymentrequestevent">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+
+service_worker_test('payment-request-event-constructor.https.serviceworker.js',
+ 'PaymentRequestEvent can be constructed in service worker');
+
+</script>
diff --git a/testing/web-platform/tests/payment-handler/payment-request-event-constructor.https.serviceworker.js b/testing/web-platform/tests/payment-handler/payment-request-event-constructor.https.serviceworker.js
new file mode 100644
index 0000000000..1aea7000c1
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/payment-request-event-constructor.https.serviceworker.js
@@ -0,0 +1,45 @@
+importScripts('/resources/testharness.js');
+
+test(() => {
+ try {
+ new PaymentRequestEvent('test', undefined);
+ new PaymentRequestEvent('test', null);
+ new PaymentRequestEvent('test', {});
+ } catch (err) {
+ assert_unreached(`Unexpected exception: ${err.message}`);
+ }
+}, 'PaymentRequestEvent can be constucted in service worker.');
+
+test(() => {
+ const ev = new PaymentRequestEvent('test', {
+ bubbles: true,
+ cancelable: true,
+ composed: true,
+ });
+ assert_false(ev.isTrusted, 'constructed in script, so not be trusted');
+ assert_true(ev.bubbles, 'set by EventInitDict');
+ assert_true(ev.cancelable, 'set by EventInitDict');
+ assert_true(ev.composed, 'set by EventInitDict');
+ assert_equals(ev.target, null, 'initially null');
+ assert_equals(ev.type, 'test');
+}, 'PaymentRequestEvent can be constructed with an EventInitDict, even if not trusted');
+
+test(() => {
+ const ev = new PaymentRequestEvent('test', {
+ topOrigin: 'https://foo.com',
+ paymentRequestOrigin: 'https://bar.com',
+ methodData: [],
+ modifiers: [],
+ });
+ assert_false(ev.isTrusted, 'constructed in script, so not be trusted');
+ assert_equals(ev.topOrigin, 'https://foo.com');
+ assert_equals(ev.paymentRequestOrigin, 'https://bar.com');
+}, 'PaymentRequestEvent can be constructed with a PaymentRequestEventInit, even if not trusted');
+
+test(() => {
+ const ev = new PaymentRequestEvent('test', {});
+ self.addEventListener('test', evt => {
+ assert_equals(ev, evt);
+ });
+ self.dispatchEvent(ev);
+}, 'PaymentRequestEvent can be dispatched, even if not trusted');
diff --git a/testing/web-platform/tests/payment-handler/payment-request-event-constructor.https.worker.js b/testing/web-platform/tests/payment-handler/payment-request-event-constructor.https.worker.js
new file mode 100644
index 0000000000..fdb71aa845
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/payment-request-event-constructor.https.worker.js
@@ -0,0 +1,7 @@
+importScripts('/resources/testharness.js');
+
+test(() => {
+ assert_false('PaymentRequestEvent' in self);
+}, 'PaymentRequestEvent constructor must not be exposed in worker');
+
+done();
diff --git a/testing/web-platform/tests/payment-handler/payment-request-event-manual.https.html b/testing/web-platform/tests/payment-handler/payment-request-event-manual.https.html
new file mode 100644
index 0000000000..e595dd2160
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/payment-request-event-manual.https.html
@@ -0,0 +1,88 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Tests for PaymentRequestEvent</title>
+<link rel="help" href="https://w3c.github.io/payment-handler/#the-paymentrequestevent">
+<link rel="manifest" href="/payment-handler/basic-card.json">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="register-and-activate-service-worker.js"></script>
+<p>When the payment sheet is shown, please authorize the mock payment.</p>
+<script>
+async function setInstrumentsAndRunTests(registration) {
+ const methodName = window.location.origin + '/payment-handler/payment-app/';
+ await registration.paymentManager.instruments.clear();
+ await registration.paymentManager.instruments.set('instrument-key', {
+ name: 'Instrument Name',
+ icons: [
+ {src: '/images/rgrg-256x256.png', sizes: '256x256', type: 'image/png'},
+ ],
+ method: methodName,
+ capabilities: {supportedNetworks: ['mir']},
+ });
+ runTests();
+}
+
+function runTests() {
+ promise_test(async t => {
+ const response = await new PaymentRequest(
+ [
+ {supportedMethods: methodName, data: {}},
+ {supportedMethods: 'interledger', data: {supportedNetworks: ['mir']}},
+ ],
+ {
+ id: 'test-payment-request-identifier',
+ total: {label: 'Total', amount: {currency: 'USD', value: '0.01'}},
+ displayItems: [
+ {label: 'Item 1', amount: {currency: 'CAD', value: '0.005'}},
+ {label: 'Item 2', amount: {currency: 'EUR', value: '0.005'}},
+ ],
+ modifiers: [
+ {
+ supportedMethods: methodName,
+ data: {supportedNetworks: ['mir']},
+ total: {
+ label: 'MIR total',
+ amount: {currency: 'USD', value: '0.0099'},
+ },
+ additionalDisplayItems: [
+ {label: 'Item 3', amount: {currency: 'GBP', value: '-0.0001'}},
+ ],
+ },
+ {
+ supportedMethods: methodName,
+ data: {supportedNetworks: ['visa']},
+ total: {
+ label: 'VISA total',
+ amount: {currency: 'USD', value: '0.0098'},
+ },
+ additionalDisplayItems: [
+ {label: 'Item 4', amount: {currency: 'CNY', value: '-0.0002'}},
+ ],
+ },
+ {
+ supportedMethods: 'interledger',
+ data: {},
+ total: {
+ label: 'Prepaid total',
+ amount: {currency: 'USD', value: '0.0097'},
+ },
+ additionalDisplayItems: [
+ {label: 'Item 5', amount: {currency: 'JPY', value: '-0.0003'}},
+ ],
+ },
+ ],
+ },
+ ).show();
+ const promise = response.complete('success');
+ assert_equals(response.requestId, 'test-payment-request-identifier');
+ assert_equals(response.methodName, methodName);
+ return promise;
+ }, 'Can perform payment');
+}
+
+registerAndActiveServiceWorker(
+ 'app-simple.js',
+ 'payment-app/',
+ setInstrumentsAndRunTests,
+);
+</script>
diff --git a/testing/web-platform/tests/payment-handler/register-and-activate-service-worker.js b/testing/web-platform/tests/payment-handler/register-and-activate-service-worker.js
new file mode 100644
index 0000000000..fb54c5c064
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/register-and-activate-service-worker.js
@@ -0,0 +1,28 @@
+async function registerAndActiveServiceWorker(script, scope, callback) {
+ const registration = await navigator.serviceWorker.register(script, {scope});
+ const serviceWorker =
+ registration.installing || registration.waiting || registration.active;
+ if (serviceWorker) {
+ waitForServiceWorkerActivation(scope, callback);
+ return;
+ }
+
+ registration.addEventListener('updatefound', event => {
+ waitForServiceWorkerActivation(scope, callback);
+ });
+}
+
+async function waitForServiceWorkerActivation(scope, callback) {
+ const registration = await navigator.serviceWorker.getRegistration(scope);
+ if (registration.active) {
+ callback(registration);
+ return;
+ }
+
+ const serviceWorker = registration.installing || registration.waiting;
+ serviceWorker.addEventListener('statechange', event => {
+ if (event.target.state == 'activated') {
+ callback(registration);
+ }
+ });
+}
diff --git a/testing/web-platform/tests/payment-handler/same-object-attributes.https.html b/testing/web-platform/tests/payment-handler/same-object-attributes.https.html
new file mode 100644
index 0000000000..2e5dea3a4a
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/same-object-attributes.https.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<link rel="help" href="https://w3c.github.io/payment-handler/">
+<title>Test for [SameObject] attributes</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+
+promise_test(async t => {
+ const registration = await service_worker_unregister_and_register(
+ t, 'app-simple.js', 'payment-app/');
+ await wait_for_state(t, registration.installing, 'activated');
+
+ assert_equals(registration.paymentManager, registration.paymentManager);
+ assert_equals(registration.paymentManager.instruments, registration.paymentManager.instruments);
+});
+
+</script>
diff --git a/testing/web-platform/tests/payment-handler/supports-shipping-contact-delegation-manual.https.html b/testing/web-platform/tests/payment-handler/supports-shipping-contact-delegation-manual.https.html
new file mode 100644
index 0000000000..939e542926
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/supports-shipping-contact-delegation-manual.https.html
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Tests for Delegation of shipping and contact collection to PH</title>
+<link rel="manifest" href="/payment-handler/manifest.json" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="register-and-activate-service-worker.js"></script>
+<p>If the payment sheet is shown, please authorize the mock payment.</p>
+<script>
+ async function runTests(registration) {
+ const methodName = window.location.origin + '/payment-handler/payment-app/';
+ await registration.paymentManager.instruments.clear();
+ await registration.paymentManager.instruments.set('instrument-key', {
+ name: 'Instrument Name',
+ method: methodName,
+ });
+ await navigator.serviceWorker.ready;
+ await registration.paymentManager.enableDelegations(
+ ['shippingAddress', 'payerName', 'payerPhone', 'payerEmail']);
+
+ promise_test(async (t) => {
+ const request = new PaymentRequest([{supportedMethods: methodName}], {
+ total: {label: 'Total', amount: {currency: 'USD', value: '0.01'}},
+ shippingOptions: [{
+ id: 'freeShippingOption',
+ label: 'Free global shipping',
+ amount: {
+ currency: 'USD',
+ value: '0',
+ },
+ selected: true,
+ }],
+ }, {requestShipping: true});
+
+ const response = await test_driver.bless('showing a payment sheet', () =>
+ request.show()
+ );
+ const complete_promise = response.complete('success');
+
+ // Validate response
+ assert_equals('freeShippingOption', response.shippingOption);
+ assert_equals('Reston', response.shippingAddress.city);
+ assert_equals('US', response.shippingAddress.country);
+ assert_equals('20190', response.shippingAddress.postalCode);
+ assert_equals('VA', response.shippingAddress.region);
+
+ return complete_promise;
+ }, 'Payment handler response should include shipping address and selected shipping option id.');
+
+ promise_test(async (t) => {
+ const request = new PaymentRequest([{
+ supportedMethods: methodName
+ }], {
+ total: {
+ label: 'Total',
+ amount: {
+ currency: 'USD',
+ value: '0.01'
+ }
+ }
+ }, {
+ requestPayerName: true,
+ requestPayerEmail: true,
+ requestPayerPhone: true
+ });
+
+ const response = await test_driver.bless('showing a payment sheet', () =>
+ request.show()
+ );
+ const complete_promise = response.complete('success');
+
+ // Validate response.
+ assert_equals('John Smith', response.payerName);
+ assert_equals('smith@gmail.com', response.payerEmail);
+ assert_equals('+15555555555', response.payerPhone);
+
+ return complete_promise;
+ }, 'Payment handler response should include payer\'s contact information.');
+ }
+
+ registerAndActiveServiceWorker(
+ 'app-supports-shipping-contact-delegation.js',
+ 'payment-app/',
+ runTests
+ );
+</script>
diff --git a/testing/web-platform/tests/payment-handler/untrusted-event.https.html b/testing/web-platform/tests/payment-handler/untrusted-event.https.html
new file mode 100644
index 0000000000..900ac79d0d
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/untrusted-event.https.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<link rel="help" href="https://w3c.github.io/payment-handler/">
+<title>Test for untrusted event</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+
+async function getResultFromSW(serviceWorkerContainer) {
+ return new Promise((resolve, reject) => {
+ serviceWorkerContainer.addEventListener('message', listener = e => {
+ serviceWorkerContainer.removeEventListener('message', listener);
+ if (e.data) {
+ resolve(e.data);
+ } else {
+ reject();
+ }
+ });
+ });
+}
+
+promise_test(async t => {
+ const registration = await service_worker_unregister_and_register(
+ t, 'untrusted-event.js', 'payment-app/');
+ await wait_for_state(t, registration.installing, 'activated');
+
+ const controlled_window = (await with_iframe('payment-app/payment.html')).contentWindow;
+
+ // Test for untrusted PaymentRequestEvent
+ {
+ const result = getResultFromSW(controlled_window.navigator.serviceWorker);
+ controlled_window.navigator.serviceWorker.controller.postMessage('paymentrequest');
+
+ const expected = [
+ "InvalidStateError", /* respondWith */
+ "InvalidStateError" /* openWindow */
+ ];
+
+ assert_array_equals(await result, expected);
+ }
+
+ // Test for untrusted CanMakePaymentEvent
+ {
+ const result = getResultFromSW(controlled_window.navigator.serviceWorker);
+ controlled_window.navigator.serviceWorker.controller.postMessage('canmakepayment');
+
+ const expected = [
+ "InvalidStateError", /* respondWith */
+ ];
+
+ assert_array_equals(await result, expected);
+ }
+});
+
+</script>
diff --git a/testing/web-platform/tests/payment-handler/untrusted-event.js b/testing/web-platform/tests/payment-handler/untrusted-event.js
new file mode 100644
index 0000000000..e067952cc3
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/untrusted-event.js
@@ -0,0 +1,59 @@
+let sender = null;
+
+self.addEventListener('message', e => {
+ sender = e.source;
+
+ if (e.data == 'paymentrequest') {
+ self.dispatchEvent(new PaymentRequestEvent('paymentrequest', {
+ methodData: [{
+ supportedMethods: 'https://example.com/pay'
+ }],
+ total: {
+ currency: 'USD',
+ value: '100'
+ },
+ modifiers: [{
+ supportedMethods: 'https://example.com/pay'
+ }]
+ }));
+ } else if (e.data == 'canmakepayment') {
+ self.dispatchEvent(new CanMakePaymentEvent('canmakepayment', {
+ methodData: [{
+ supportedMethods: 'https://example.com/pay'
+ }],
+ modifiers: [{
+ supportedMethods: 'https://example.com/pay'
+ }]
+ }));
+ }
+});
+
+self.addEventListener('paymentrequest', async e => {
+ const result = [];
+
+ try {
+ e.respondWith({});
+ } catch (exception) {
+ result.push(exception.name);
+ }
+
+ try {
+ await e.openWindow('payment-app/payment.html');
+ } catch (exception) {
+ result.push(exception.name);
+ }
+
+ sender.postMessage(result);
+});
+
+self.addEventListener('canmakepayment', async e => {
+ const result = [];
+
+ try {
+ e.respondWith({});
+ } catch (exception) {
+ result.push(exception.name);
+ }
+
+ sender.postMessage(result);
+});