summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/payment-handler
diff options
context:
space:
mode:
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.js57
-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.js84
-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-manifest.json15
-rw-r--r--testing/web-platform/tests/payment-handler/can-make-payment-event.https.html270
-rw-r--r--testing/web-platform/tests/payment-handler/change-payment-method-manual-manifest.json15
-rw-r--r--testing/web-platform/tests/payment-handler/change-payment-method-manual.https.html156
-rw-r--r--testing/web-platform/tests/payment-handler/change-shipping-address-manual-manifest.json20
-rw-r--r--testing/web-platform/tests/payment-handler/change-shipping-address-manual.https.html145
-rw-r--r--testing/web-platform/tests/payment-handler/change-shipping-option-manual-manifest.json20
-rw-r--r--testing/web-platform/tests/payment-handler/change-shipping-option-manual.https.html144
-rw-r--r--testing/web-platform/tests/payment-handler/idlharness.https.any.js45
-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-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-manifest.json15
-rw-r--r--testing/web-platform/tests/payment-handler/payment-request-event-manual.https.html69
-rw-r--r--testing/web-platform/tests/payment-handler/same-object-attributes.https.html18
-rw-r--r--testing/web-platform/tests/payment-handler/supports-shipping-contact-delegation-manual-manifest.json23
-rw-r--r--testing/web-platform/tests/payment-handler/supports-shipping-contact-delegation-manual.https.html70
-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
33 files changed, 1622 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..6b060dffb7
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/app-can-make-payment.js
@@ -0,0 +1,57 @@
+let responseType = 'canMakePayment-true';
+
+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;
+ }
+});
+
+self.addEventListener('paymentrequest', event => {
+ responseType = event.methodData[0].data.responseType;
+ event.respondWith({
+ methodName: event.methodData[0].supportedMethods,
+ details: {status: 'success'},
+ });
+});
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..9e70c08246
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/app-simple.js
@@ -0,0 +1,84 @@
+self.addEventListener('canmakepayment', event => {
+ event.respondWith(true);
+});
+
+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 expectedMethodNamePrefix = 'http';
+ if (!methodData.supportedMethods.startsWith(expectedMethodNamePrefix)) {
+ const msg = `Expected payment method name "${methodData.supportedMethods}" to start with ${expectedMethodNamePrefix}"`;
+ event.respondWith(Promise.reject(new Error(msg)));
+ return;
+ }
+
+ const expectedMethodNameSuffix = '/payment-handler/payment-request-event-manual-manifest.json';
+ if (!methodData.supportedMethods.endsWith(expectedMethodNameSuffix)) {
+ const msg = `Expected payment method name "${methodData.supportedMethods}" to end with ${expectedMethodNameSuffix}"`;
+ 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: methodData.supportedMethods,
+ details: {status: 'success'},
+ });
+});
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-manifest.json b/testing/web-platform/tests/payment-handler/can-make-payment-event-manifest.json
new file mode 100644
index 0000000000..b31ea5ce08
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/can-make-payment-event-manifest.json
@@ -0,0 +1,15 @@
+{
+ "default_applications": ["can-make-payment-event-manifest.json"],
+ "name": "Test Payment Handler",
+ "icons": [
+ {
+ "src": "/images/rgrg-256x256.png",
+ "sizes": "256x256",
+ "type": "image/png"
+ }
+ ],
+ "serviceworker": {
+ "src": "app-can-make-payment.js",
+ "scope": "can-make-payment-event-payment-app/"
+ }
+}
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..776c68be43
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/can-make-payment-event.https.html
@@ -0,0 +1,270 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Tests for CanMakePaymentEvent</title>
+<link rel="help" href="https://w3c.github.io/payment-handler/#the-canmakepaymentevent">
+<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 DEFAULT_METHOD_NAME = window.location.origin + '/payment-handler/'
+ + 'can-make-payment-event-manifest.json';
+
+async function registerApp(responseType = 'canMakePayment-true') {
+ const request = new PaymentRequest(
+ [{supportedMethods: DEFAULT_METHOD_NAME, data: {responseType}}],
+ {total: {label: 'Total', amount: {currency: 'USD', value: '0.01'}}});
+ const response = await test_driver.bless('installing a payment app', () =>
+ request.show()
+ );
+ return response.complete('success');
+}
+
+function buildPaymentRequest(methodName = DEFAULT_METHOD_NAME) {
+ 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 => {
+ // Intentionally do not install the payment app.
+ const request = buildPaymentRequest();
+ 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('installing a payment app just-in-time');
+ const response = await request.show();
+ assert_equals('success', response.details.status);
+ return response.complete('success');
+}, 'If a payment handler is not installed, but can be installed just-in-time, then the payment method is supported.');
+
+promise_test(async t => {
+ // Intentionally do not install the payment app.
+ const request = buildPaymentRequest(DEFAULT_METHOD_NAME + '/non-existent');
+ 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.',
+ );
+
+ const response = await test_driver.bless('invoking a payemnt app');
+ await promise_rejects_dom(t, 'NotSupportedError', request.show());
+}, 'If a payment handler is not installed and cannot be installed just-in-time, then the payment method is not supported.');
+
+promise_test(async t => {
+ await registerApp('canMakePayment-false');
+ const request = buildPaymentRequest();
+ 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('invoking a payment app');
+ const response = await request.show();
+ assert_equals('success', response.details.status);
+ return response.complete('success');
+}, 'If CanMakePaymentEvent.respondWith(false) is called, then canMakePayment() still returns true and the app can still be invoked.');
+
+promise_test(async t => {
+ await registerApp('canMakePayment-promise-false');
+ const request = buildPaymentRequest();
+ 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('invoking a payment app');
+ const response = await request.show();
+ assert_equals('success', response.details.status);
+ return response.complete('success');
+}, 'If CanMakePaymentEvent.respondWith(Promise.resolve(false)) is called, then canMakePayment() still returns true and the app can still be invoked.');
+
+promise_test(async t => {
+ await registerApp('canMakePayment-true');
+ const request = buildPaymentRequest();
+ 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('invoking a payment app');
+ const response = await request.show();
+ assert_equals('success', response.details.status);
+ return response.complete('success');
+}, 'If CanMakePaymentEvent.respondWith(true) is called, then canMakePayment() returns true and the app can be invoked.');
+
+promise_test(async t => {
+ await registerApp('canMakePayment-promise-true');
+ const request = buildPaymentRequest();
+ 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('invoking a payment app');
+ const response = await request.show();
+ assert_equals('success', response.details.status);
+ return response.complete('success');
+}, 'If CanMakePaymentEvent.respondWith(Promise.resolve(true)) is called, then canMakePayment() returns true and the app can be invoked.');
+
+promise_test(async t => {
+ await registerApp('canMakePayment-custom-error');
+ const request = buildPaymentRequest();
+ 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('invoking a payment app');
+ const response = await request.show();
+ assert_equals('success', response.details.status);
+ return response.complete('success');
+}, 'If CanMakePaymentEvent.respondWith(Promise.reject(error)) is called, then canMakePayment() still returns true and the app can still be invoked.');
+</script>
diff --git a/testing/web-platform/tests/payment-handler/change-payment-method-manual-manifest.json b/testing/web-platform/tests/payment-handler/change-payment-method-manual-manifest.json
new file mode 100644
index 0000000000..7a4f5d6f7e
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/change-payment-method-manual-manifest.json
@@ -0,0 +1,15 @@
+{
+ "default_applications": ["change-payment-method-manual-manifest.json"],
+ "name": "Test Payment Handler",
+ "icons": [
+ {
+ "src": "/images/rgrg-256x256.png",
+ "sizes": "256x256",
+ "type": "image/png"
+ }
+ ],
+ "serviceworker": {
+ "src": "app-change-payment-method.js",
+ "scope": "change-payment-method-manual-payment-app/"
+ }
+}
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..15165b91d7
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/change-payment-method-manual.https.html
@@ -0,0 +1,156 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Tests for PaymentRequestEvent.changePaymentMethod()</title>
+<link
+ rel="help"
+ href="https://w3c.github.io/payment-handler/#changepaymentmethod-method"
+/>
+<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>
+<p>If the payment sheet is shown, please authorize the mock payment.</p>
+<script>
+const methodName = window.location.origin
+ + '/payment-handler/change-payment-method-manual-manifest.json';
+
+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.');
+</script>
diff --git a/testing/web-platform/tests/payment-handler/change-shipping-address-manual-manifest.json b/testing/web-platform/tests/payment-handler/change-shipping-address-manual-manifest.json
new file mode 100644
index 0000000000..08b034e825
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/change-shipping-address-manual-manifest.json
@@ -0,0 +1,20 @@
+{
+ "default_applications": ["change-shipping-address-manual-manifest.json"],
+ "name": "Test Payment Handler",
+ "icons": [
+ {
+ "src": "/images/rgrg-256x256.png",
+ "sizes": "256x256",
+ "type": "image/png"
+ }
+ ],
+ "serviceworker": {
+ "src": "app-change-shipping-address.js",
+ "scope": "change-shipping-address-manual-payment-app/"
+ },
+ "payment": {
+ "supported_delegations": [
+ "shippingAddress"
+ ]
+ }
+}
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..e8817459c3
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/change-shipping-address-manual.https.html
@@ -0,0 +1,145 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Tests for PaymentRequestEvent.changeShippingAddress()</title>
+<link
+ rel="help"
+ href="https://w3c.github.io/payment-handler/#changeshippingaddress-method"
+/>
+
+<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>
+<p>If the payment sheet is shown, please authorize the mock payment.</p>
+<script>
+const methodName = window.location.origin
+ + '/payment-handler/change-shipping-address-manual-manifest.json';
+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});
+}
+
+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.');
+</script>
diff --git a/testing/web-platform/tests/payment-handler/change-shipping-option-manual-manifest.json b/testing/web-platform/tests/payment-handler/change-shipping-option-manual-manifest.json
new file mode 100644
index 0000000000..2c023902b2
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/change-shipping-option-manual-manifest.json
@@ -0,0 +1,20 @@
+{
+ "default_applications": ["change-shipping-option-manual-manifest.json"],
+ "name": "Test Payment Handler",
+ "icons": [
+ {
+ "src": "/images/rgrg-256x256.png",
+ "sizes": "256x256",
+ "type": "image/png"
+ }
+ ],
+ "serviceworker": {
+ "src": "app-change-shipping-option.js",
+ "scope": "change-shipping-option-manual-payment-app/"
+ },
+ "payment": {
+ "supported_delegations": [
+ "shippingAddress"
+ ]
+ }
+}
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..6e97f2878a
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/change-shipping-option-manual.https.html
@@ -0,0 +1,144 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Tests for PaymentRequestEvent.changeShippingOption()</title>
+<link
+ rel="help"
+ href="https://w3c.github.io/payment-handler/#changeshippingoption-method"
+/>
+
+<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>
+<p>If the payment sheet is shown, please authorize the mock payment.</p>
+<script>
+const methodName = window.location.origin
+ + '/payment-handler/change-shipping-option-manual-manifest.json';
+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});
+}
+
+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.');
+</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..d34e20630b
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/idlharness.https.any.js
@@ -0,0 +1,45 @@
+// 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'],
+ });
+ }
+ 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;
+ }
+ }
+);
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-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-manifest.json b/testing/web-platform/tests/payment-handler/payment-request-event-manual-manifest.json
new file mode 100644
index 0000000000..f97229762c
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/payment-request-event-manual-manifest.json
@@ -0,0 +1,15 @@
+{
+ "default_applications": ["payment-request-event-manual-manifest.json"],
+ "name": "Test Payment Handler",
+ "icons": [
+ {
+ "src": "/images/rgrg-256x256.png",
+ "sizes": "256x256",
+ "type": "image/png"
+ }
+ ],
+ "serviceworker": {
+ "src": "app-simple.js",
+ "scope": "payment-request-event-manual-payment-app/"
+ }
+}
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..9cd2035463
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/payment-request-event-manual.https.html
@@ -0,0 +1,69 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Tests for PaymentRequestEvent</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="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<p>When the payment sheet is shown, please authorize the mock payment.</p>
+<script>
+promise_test(async t => {
+ const methodName = window.location.origin + '/payment-handler/'
+ + 'payment-request-event-manual-manifest.json';
+ await test_driver.bless('invoking a payment app');
+ 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');
+</script>
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..27c2044843
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/same-object-attributes.https.html
@@ -0,0 +1,18 @@
+<!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);
+});
+
+</script>
diff --git a/testing/web-platform/tests/payment-handler/supports-shipping-contact-delegation-manual-manifest.json b/testing/web-platform/tests/payment-handler/supports-shipping-contact-delegation-manual-manifest.json
new file mode 100644
index 0000000000..fd6bc89ef6
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/supports-shipping-contact-delegation-manual-manifest.json
@@ -0,0 +1,23 @@
+{
+ "default_applications": ["supports-shipping-contact-delegation-manual-manifest.json"],
+ "name": "Test Payment Handler",
+ "icons": [
+ {
+ "src": "/images/rgrg-256x256.png",
+ "sizes": "256x256",
+ "type": "image/png"
+ }
+ ],
+ "serviceworker": {
+ "src": "app-supports-shipping-contact-delegation.js",
+ "scope": "supports-shipping-contact-delegation-manual-payment-app/"
+ },
+ "payment": {
+ "supported_delegations": [
+ "shippingAddress",
+ "payerName",
+ "payerPhone",
+ "payerEmail"
+ ]
+ }
+}
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..e3074b4e5e
--- /dev/null
+++ b/testing/web-platform/tests/payment-handler/supports-shipping-contact-delegation-manual.https.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Tests for Delegation of shipping and contact collection to PH</title>
+<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>
+<p>If the payment sheet is shown, please authorize the mock payment.</p>
+<script>
+const methodName = window.location.origin + '/payment-handler/'
+ + 'supports-shipping-contact-delegation-manual-manifest.json';
+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.');
+</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);
+});