summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/payment-request
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/payment-request')
-rw-r--r--testing/web-platform/tests/payment-request/PaymentAddress/attributes-and-toJSON-method-manual.https.html109
-rw-r--r--testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-call-immediate-manual.https.html206
-rw-r--r--testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-duplicate-shipping-options-manual.https.html106
-rw-r--r--testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-incremental-update-manual.https.html196
-rw-r--r--testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-method-abort-update-manual.https.html286
-rw-r--r--testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-state-checks-manual.https.html125
-rw-r--r--testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updatewith-method.https.html8
-rw-r--r--testing/web-platform/tests/payment-request/PaymentValidationErrors/retry-shows-shippingAddress-member-manual.https.html103
-rw-r--r--testing/web-platform/tests/payment-request/algorithms-manual.https.html176
-rw-r--r--testing/web-platform/tests/payment-request/billing-address-changed-manual.https.html115
-rw-r--r--testing/web-platform/tests/payment-request/change-shipping-option-manual.https.html104
-rw-r--r--testing/web-platform/tests/payment-request/change-shipping-option-select-last-manual.https.html101
-rw-r--r--testing/web-platform/tests/payment-request/dynamically-change-shipping-options-manual.https.html142
-rw-r--r--testing/web-platform/tests/payment-request/historical.https.html4
-rw-r--r--testing/web-platform/tests/payment-request/payment-request-constructor-thcrash.https.html254
-rw-r--r--testing/web-platform/tests/payment-request/payment-request-constructor.https.sub.html222
-rw-r--r--testing/web-platform/tests/payment-request/payment-request-ctor-currency-code-checks.https.sub.html57
-rw-r--r--testing/web-platform/tests/payment-request/payment-request-onshippingaddresschange-attribute.https.html78
-rw-r--r--testing/web-platform/tests/payment-request/payment-request-onshippingoptionchange-attribute.https.html79
-rw-r--r--testing/web-platform/tests/payment-request/payment-request-shippingAddress-attribute.https.html28
-rw-r--r--testing/web-platform/tests/payment-request/payment-request-shippingOption-attribute.https.html100
-rw-r--r--testing/web-platform/tests/payment-request/payment-request-shippingType-attribute.https.html72
-rw-r--r--testing/web-platform/tests/payment-request/payment-response/helpers.js40
-rw-r--r--testing/web-platform/tests/payment-request/payment-response/retry-method-manual.https.html296
-rw-r--r--testing/web-platform/tests/payment-request/payment-response/retry-method-warnings-manual.https.html158
-rw-r--r--testing/web-platform/tests/payment-request/payment-response/shippingAddress-attribute-manual.https.html101
-rw-r--r--testing/web-platform/tests/payment-request/payment-response/shippingOption-attribute-manual.https.html43
-rw-r--r--testing/web-platform/tests/payment-request/shipping-address-changed-manual.https.html99
-rw-r--r--testing/web-platform/tests/payment-request/show-method-optional-promise-rejects.https.html40
-rw-r--r--testing/web-platform/tests/payment-request/show-method-optional-promise-resolves-manual.https.html339
-rw-r--r--testing/web-platform/tests/payment-request/updateWith-method-pmi-handling-manual.https.html140
-rw-r--r--testing/web-platform/tests/payment-request/user-accepts-payment-request-algo-manual.https.html230
32 files changed, 4151 insertions, 6 deletions
diff --git a/testing/web-platform/tests/payment-request/PaymentAddress/attributes-and-toJSON-method-manual.https.html b/testing/web-platform/tests/payment-request/PaymentAddress/attributes-and-toJSON-method-manual.https.html
new file mode 100644
index 0000000000..fc1ce3523e
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/PaymentAddress/attributes-and-toJSON-method-manual.https.html
@@ -0,0 +1,109 @@
+<!doctype html>
+<meta charset="utf8">
+<link rel="help" href="https://www.w3.org/TR/payment-request/#ContactAddress-interface">
+<title>
+ PaymentResponse.prototype.shippingAddress
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../payment-response/helpers.js"></script>
+<script>
+const options = { requestShipping: true };
+function runManualTest(button, expected = {}) {
+ button.disabled = true;
+ promise_test(async () => {
+ const { response } = await getPaymentRequestResponse(options);
+ await response.complete();
+ assert_idl_attribute(response, "shippingAddress");
+ const { shippingAddress: addr } = response;
+ assert_true(
+ addr instanceof ContactAddress,
+ "Expect instance of ContactAddress"
+ );
+ // An [ISO3166] alpha-2 code. The canonical form is upper case.
+ const { country } = addr;
+ assert_equals(country.length, 2, "Expected length is 2");
+ assert_true(/^[A-Z]{2}$/.test(country), "Canonical form is upper case");
+ assert_true(
+ addr.addressLine instanceof Array,
+ "Expected addressLine to be an array"
+ );
+ assert_throws_js(
+ TypeError,
+ () => {
+ addr.addressLine.push("this must throw");
+ },
+ "Array must be frozen"
+ );
+ for (let [attr, expectedValue] of Object.entries(expected)) {
+ assert_idl_attribute(addr, attr);
+ const msg = `Expected ContactAddress.${attr} to equal ${expectedValue}.`;
+ //.toString() flattens array addressLine,
+ //.toLowerCase() because case can't be enforced for some attributes
+ const actualValue = addr[attr].toString().toLowerCase();
+ expectedValue = expectedValue.toString().toLowerCase();
+ assert_equals(actualValue, expectedValue, msg);
+ }
+ // Check toJSON result
+ for (let [prop, jsonValue] of Object.entries(addr.toJSON())) {
+ const actualValue = jsonValue.toString().toLowerCase();
+ const expectedValue = expected[prop].toString().toLowerCase();
+ const msg = `Expected JSON ${prop} to be ${expectedValue}`;
+ assert_equals(actualValue, expectedValue, msg);
+ }
+ }, button.textContent.trim());
+ done();
+}
+</script>
+<h2>ContactAddress interface</h2>
+<p>
+ Click on each button in sequence from top to bottom without refreshing the page.
+ Each button will bring up the Payment Request UI window.
+</p>
+<p>
+ When prompted, please enter addresses as follows...
+</p>
+<ol>
+ <li>
+ <button onclick="
+ const expectedAddress = {
+ country: 'AU',
+ regionCode: 'QLD',
+ addressLine: '55 test st',
+ city: 'Chapel Hill',
+ dependentLocality: '',
+ postalCode: '6095',
+ region: 'QLD',
+ sortingCode: '',
+ organization: 'w3c',
+ recipient: 'web platform test',
+ phone: '+61733780000',
+ };
+ runManualTest(this, expectedAddress);">
+ If the requestShipping member is true, then shippingAddress's ContactAddress must match the expected values.
+ </button>
+ Please use:
+ <dl>
+ <dt>Recipient:</dt>
+ <dd>web platform test</dd>
+ <dt>Address line:</dt>
+ <dd>55 test st</dd>
+ <dt>Country</dt>
+ <dd>Australia</dd>
+ <dt>City</dt>
+ <dd>Chapel Hill</dd>
+ <dd>State/Region</dd>
+ <dd>Queensland</dd>
+ <dt>postal code </dt>
+ <dd>6095</dd>
+ <dt>organization</dt>
+ <dd>w3c</dd>
+ <dt>Phone number</dt>
+ <dd>+61 7 3378 0000</dd>
+ </dl>
+ </li>
+</ol>
+<small>
+ If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a>
+ and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>.
+</small>
diff --git a/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-call-immediate-manual.https.html b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-call-immediate-manual.https.html
new file mode 100644
index 0000000000..1365ecefeb
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-call-immediate-manual.https.html
@@ -0,0 +1,206 @@
+<!doctype html>
+<meta charset="utf8">
+<link rel="help" href="https://www.w3.org/TR/payment-request/#updatewith()-method">
+<link rel="help" href="https://github.com/w3c/payment-request/pull/591">
+<title>
+ PaymentRequestUpdateEvent.updateWith() needs to be called immediately
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({ explicit_done: true, explicit_timeout: true });
+const applePay = Object.freeze({
+ supportedMethods: "https://apple.com/apple-pay",
+ data: {
+ version: 3,
+ merchantIdentifier: "merchant.com.example",
+ countryCode: "US",
+ merchantCapabilities: ["supports3DS"],
+ supportedNetworks: ["visa"],
+ }
+});
+const validMethod = Object.freeze({ supportedMethods: "basic-card" });
+const validMethods = Object.freeze([validMethod, applePay]);
+const validAmount = Object.freeze({ currency: "USD", value: "5.00" });
+const validTotal = Object.freeze({
+ label: "label",
+ amount: validAmount,
+});
+const validShippingOptionA = Object.freeze({
+ id: "a-shipping-option",
+ label: "A shipping option",
+ amount: validAmount,
+ selected: true,
+});
+const validShippingOptionB = Object.freeze({
+ id: "b-shipping-option",
+ label: "B shipping option",
+ amount: validAmount,
+});
+const validDetails = Object.freeze({
+ total: validTotal,
+ shippingOptions: [validShippingOptionA, validShippingOptionB],
+});
+const validOptions = Object.freeze({
+ requestShipping: true,
+});
+
+function testImmediateUpdate({ textContent: testName }) {
+ promise_test(async t => {
+ const request = new PaymentRequest(
+ validMethods,
+ validDetails,
+ validOptions
+ );
+ const eventPromise = new Promise((resolve, reject) => {
+ request.addEventListener(
+ "shippingaddresschange",
+ ev => {
+ // Forces updateWith() to be run in the next event loop tick so that
+ // [[waitForUpdate]] is already true when it runs.
+ t.step_timeout(() => {
+ try {
+ ev.updateWith(validDetails);
+ resolve(); // This is bad.
+ } catch (err) {
+ reject(err); // this is good.
+ }
+ });
+ },
+ { once: true }
+ );
+ });
+ const acceptPromise = request.show();
+ await promise_rejects_dom(
+ t,
+ "InvalidStateError",
+ eventPromise,
+ "The event loop already spun, so [[waitForUpdate]] is now true"
+ );
+ const response = await acceptPromise;
+ await response.complete();
+ }, testName.trim());
+}
+
+function testSubsequentUpdateWithCalls({ textContent: testName }) {
+ promise_test(async t => {
+ const request = new PaymentRequest(
+ validMethods,
+ validDetails,
+ validOptions
+ );
+ const eventPromise = new Promise((resolve, reject) => {
+ request.addEventListener("shippingaddresschange", async ev => {
+ const p = Promise.resolve(validDetails);
+ ev.updateWith(p);
+ await p;
+ try {
+ ev.updateWith(validDetails);
+ resolve(); // this is bad, we should never get to here.
+ } catch (err) {
+ reject(err); // this is good!
+ }
+ });
+ });
+ const responsePromise = request.show();
+ await promise_rejects_dom(
+ t,
+ "InvalidStateError",
+ eventPromise,
+ "Expected eventPromise to have rejected, because updateWith() was a called twice"
+ );
+ const response = await responsePromise;
+ await response.complete();
+ }, testName.trim());
+}
+
+function testRecycleEvents({ textContent: testName }) {
+ promise_test(async t => {
+ const request = new PaymentRequest(
+ validMethods,
+ validDetails,
+ validOptions
+ );
+
+ // Register both listeners.
+ const addressChangedPromise = new Promise(resolve => {
+ request.addEventListener("shippingaddresschange", resolve, {
+ once: true,
+ });
+ });
+
+ const optionChangedPromise = new Promise(resolve => {
+ request.addEventListener("shippingoptionchange", resolve, {
+ once: true,
+ });
+ });
+
+ const responsePromise = request.show();
+
+ // Let's wait for the address to change.
+ const addressChangeEvent = await addressChangedPromise;
+
+ // Sets [[waitingForUpdate]] to true.
+ addressChangeEvent.updateWith(validDetails);
+
+ // Let's wait for the shippingOption.
+ const optionChangeEvent = await optionChangedPromise;
+
+ // Here, we try to be sneaky, and reuse the addressChangeEvent to perform the update.
+ // However, addressChangeEvent [[waitingForUpdate]] is true, so it throws.
+ assert_throws_dom(
+ "InvalidStateError",
+ () => {
+ addressChangeEvent.updateWith(validDetails);
+ },
+ "addressChangeEvent [[waitingForUpdate]] is true, so it must throw"
+ );
+
+ // But optionChangeEvent is still usable tho, so...
+ optionChangeEvent.updateWith(validDetails);
+
+ assert_throws_dom(
+ "InvalidStateError",
+ () => {
+ optionChangeEvent.updateWith(validDetails);
+ },
+ "optionChangeEvent [[waitingForUpdate]] is true, so it must throw"
+ );
+
+ const response = await responsePromise;
+ await response.complete();
+ }, testName.trim());
+}
+</script>
+<h2>updateWith() method</h2>
+<p>
+ Click on each button in sequence from top to bottom without refreshing the page.
+ Each button will bring up the Payment Request UI window.
+</p>
+<p>
+ When the payment sheet is shown, select a different shipping address once. Then pay.
+</p>
+<ol>
+ <li id="test-0">
+ <button onclick="testImmediateUpdate(this);">
+ updateWith() must be called immediately, otherwise must throw an InvalidStateError.
+ </button>
+ </li>
+ <li id="test-1">
+ <button onclick="testSubsequentUpdateWithCalls(this);">
+ Once the event has performed an update, subsequent calls to updateWith() must throw InvalidStateError.
+ </button>
+ </li>
+ <li id="test-2">
+ <button onclick="testRecycleEvents(this);">
+ Recycling events must not be possible.
+ </button> When the payment sheet is shown, select a different shipping address once, then change shipping option once. Then pay.
+ </li>
+ <li>
+ <button onclick="done();">Done!</button>
+ </li>
+</ol>
+<small>
+ If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a>
+ and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>.
+</small>
diff --git a/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-duplicate-shipping-options-manual.https.html b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-duplicate-shipping-options-manual.https.html
new file mode 100644
index 0000000000..a4a7afd7f6
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-duplicate-shipping-options-manual.https.html
@@ -0,0 +1,106 @@
+<!doctype html>
+<meta charset="utf8">
+<link rel="help" href="https://w3c.github.io/payment-request/#updatewith()-method">
+<title>
+ updateWith() method - duplicate shippingOption ids
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({ explicit_done: true, explicit_timeout: true });
+const applePay = Object.freeze({
+ supportedMethods: "https://apple.com/apple-pay",
+ data: {
+ version: 3,
+ merchantIdentifier: "merchant.com.example",
+ countryCode: "US",
+ merchantCapabilities: ["supports3DS"],
+ supportedNetworks: ["visa"],
+ }
+});
+const validMethod = Object.freeze({ supportedMethods: "basic-card" });
+const validMethods = [validMethod, applePay];
+const validAmount = Object.freeze({
+ currency: "USD",
+ value: "5.00",
+});
+const validShippingOption = Object.freeze({
+ id: "option1",
+ label: "Option 1",
+ amount: validAmount,
+ selected: true,
+});
+const validShippingOptions = Object.freeze([validShippingOption]);
+const validDetails = Object.freeze({
+ total: {
+ label: "Total due",
+ amount: validAmount,
+ },
+ shippingOptions: validShippingOptions,
+});
+const validOptions = Object.freeze({
+ requestShipping: true,
+});
+
+test(() => {
+ try {
+ const request = new PaymentRequest(validMethods, validDetails);
+ } catch (err) {
+ done();
+ throw err;
+ }
+}, "Must construct a PaymentRequest (smoke test)");
+
+function testFireEvents(button) {
+ button.disabled = true;
+ promise_test(async t => {
+ const request = new PaymentRequest(
+ validMethods,
+ validDetails,
+ validOptions
+ );
+ request.addEventListener("shippingaddresschange", event => {
+ // Same option, so duplicate ids
+ const otherShippingOption = Object.assign({}, validShippingOption, {
+ id: "other",
+ });
+ const shippingOptions = [
+ validShippingOption,
+ otherShippingOption,
+ validShippingOption,
+ ];
+ const newDetails = Object.assign({}, validDetails, { shippingOptions });
+ event.updateWith(newDetails);
+ });
+ const acceptPromise = request.show();
+ await promise_rejects_js(
+ t,
+ TypeError,
+ acceptPromise,
+ "Duplicate shippingOption ids must abort with TypeError"
+ );
+ }, button.textContent.trim());
+ done();
+}
+</script>
+<h2>updateWith() method - duplicate shippingOptions ids</h2>
+<p>
+ Click on each button in sequence from top to bottom without refreshing the page.
+ Each button will bring up the Payment Request UI window.
+</p>
+<p>
+ When the payment sheet is shown, select a different shipping address.
+ If you have to manually abort the test from the payment sheet, then the
+ test has failed.
+</p>
+<ol>
+ <li>
+ <button onclick="testFireEvents(this)">
+ If there are duplicate shippingOption ids, then abort payment request.
+ </button>
+ </li>
+</ol>
+<small>
+ If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a>
+ and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>.
+</small>
diff --git a/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-incremental-update-manual.https.html b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-incremental-update-manual.https.html
new file mode 100644
index 0000000000..c1ed1b5f68
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-incremental-update-manual.https.html
@@ -0,0 +1,196 @@
+<!doctype html>
+<meta charset="utf8">
+<link rel="help" href="https://w3c.github.io/payment-request/#updatewith-method">
+<title>
+ Incremental updates via updateWith()
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({
+ explicit_done: true,
+ explicit_timeout: true,
+});
+
+const methods = [{
+ supportedMethods: "basic-card",
+}, {
+ supportedMethods: "https://apple.com/apple-pay",
+ data: {
+ version: 3,
+ merchantIdentifier: "merchant.com.example",
+ countryCode: "US",
+ merchantCapabilities: ["supports3DS"],
+ supportedNetworks: ["visa"],
+ }
+}];
+
+const options = {
+ requestShipping: true,
+};
+
+const initialDetails = {
+ total: {
+ label: "Initial total",
+ amount: {
+ currency: "USD",
+ value: "1.0",
+ },
+ },
+ shippingOptions: [
+ {
+ id: "neutral",
+ label: "NEUTRAL SHIPPING OPTION",
+ selected: true,
+ amount: {
+ currency: "USD",
+ value: "0.00",
+ },
+ },
+ ],
+};
+
+function testFireEvent(button, updateDetails) {
+ button.disabled = true;
+ const request = new PaymentRequest(methods, initialDetails, options);
+ const handlerPromise = new Promise(resolve => {
+ request.onshippingaddresschange = event => {
+ event.updateWith(updateDetails);
+ resolve(event);
+ };
+ });
+ promise_test(async t => {
+ const response = await request.show();
+ const event = await handlerPromise;
+ await response.complete("success");
+ }, button.textContent.trim());
+}
+
+</script>
+<h2>
+ Incremental updates
+</h2>
+<p>
+ Click on each button in sequence from top to bottom without refreshing the page.
+ Each button will bring up the Payment Request UI window.
+</p>
+<p>
+ Unless stated otherwise, each test will update some part of the displayed payment sheet in
+ a manner indicated below. When prompted, please change or enter a new
+ shipping address, look for the tested change, and complete the payment.
+</p>
+<p>
+ If the payment request locks up or otherwise aborts, the test has failed.
+</p>
+<ol>
+ <li>
+ <button onclick="testFireEvent(this, {});">
+ Passing an empty dictionary does not cause the sheet to change.
+ No values in the sheet must change.
+ </button>
+ </li>
+</ol>
+
+<section>
+ <h3>Incremental updates via PaymentDetailsUpdate.total</h3>
+ <ol>
+ <li>
+ <button onclick="
+ const total = {
+ label: 'PASS',
+ amount: {
+ currency: 'XXX',
+ value: '20',
+ },
+ };
+ const updatedDetails = { total };
+ testFireEvent(this, updatedDetails);">
+ After changing shipping address, the total becomes XXX20, with the label "PASS".
+ </button>
+ </li>
+ </ol>
+</section>
+
+<section>
+ <h3>Incremental updates via PaymentDetailsBase.displayItems</h3>
+ <ol>
+ <li>
+ <button onclick="
+ const item = {
+ label: 'PASS',
+ amount: { currency: 'ABC', value: '55.00' },
+ };
+ const updatedDetails = {
+ displayItems: [ item ]
+ };
+ testFireEvent(this, updatedDetails);">
+ After changing shipping address, a new display item is shown
+ with a with label PASS, and value of ABC55.00.
+ </button>
+ </li>
+ </ol>
+</section>
+
+<section>
+ <h3>Incremental updates via PaymentDetailsBase.shippingOptions</h3>
+ <ol>
+ <li>
+ <button onclick="
+ const shippingOptions = [
+ {
+ id: 'pass',
+ label: 'PASS',
+ amount: { currency: 'USD', value: '1.00' },
+ selected: true,
+ },
+ {
+ id: 'fail',
+ label: 'FAIL IF THIS IS SELECTED',
+ amount: { currency: 'USD', value: '25.00' }
+ },
+ ];
+ const updatedDetails = {
+ shippingOptions
+ };
+ testFireEvent(this, updatedDetails);">
+ After changing shipping address, two new shipping options appear.
+ The shipping option labelled "PASS" with a value of USD1.0 is selected.
+ </button>
+ </li>
+ </ol>
+</section>
+
+<section>
+ <h3>Incremental updates via PaymentDetailsBase.modifiers</h3>
+ <ol>
+ <li>
+ <button onclick="
+ const additionalItem = {
+ label: 'PASS-DISPLAY-ITEM',
+ amount: { currency: 'USD', value: '3.00' },
+ };
+ const modifiers = [{
+ additionalDisplayItems: [ additionalItem ],
+ supportedMethods: 'basic-card',
+ total: {
+ label: 'PASS-TOTAL',
+ amount: { currency: 'USD', value: '123.00' },
+ },
+ }];
+ const updatedDetails = { modifiers };
+ testFireEvent(this, updatedDetails);">
+ After changing shipping address, a new display item is shown
+ with a with label PASS-DISPLAY-ITEM, and value of ABC55.00 and the total is
+ labelled PASS-TOTAL with a value of USD123.0
+ </button>
+ </li>
+ <li>
+ <button onclick="done()">DONE - see results</button>
+ </li>
+ </ol>
+</section>
+
+<small>
+ If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a>
+ and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>.
+</small>
diff --git a/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-method-abort-update-manual.https.html b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-method-abort-update-manual.https.html
new file mode 100644
index 0000000000..e24452c2a9
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-method-abort-update-manual.https.html
@@ -0,0 +1,286 @@
+<!doctype html>
+<meta charset="utf8">
+<link rel="help" href="https://w3c.github.io/payment-request/#dfn-abort-the-update">
+<title>
+ updateWith() method - "abort the update"
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({ explicit_done: true, explicit_timeout: true });
+
+// PaymentMethod
+const validMethod = Object.freeze({
+ supportedMethods: "valid-but-wont-ever-match",
+});
+
+const validMethodBasicCard = Object.freeze({
+ supportedMethods: "basic-card",
+});
+
+const applePay = Object.freeze({
+ supportedMethods: "https://apple.com/apple-pay",
+ data: {
+ version: 3,
+ merchantIdentifier: "merchant.com.example",
+ countryCode: "US",
+ merchantCapabilities: ["supports3DS"],
+ supportedNetworks: ["visa"],
+ }
+});
+
+// Methods
+const validMethods = Object.freeze([validMethodBasicCard, validMethod, applePay]);
+
+// Amounts
+const validAmount = Object.freeze({
+ currency: "USD",
+ value: "1.00",
+});
+
+const invalidAmount = Object.freeze({
+ currency: "Β‘INVALID!",
+ value: "A1.0",
+});
+
+const negativeAmount = Object.freeze({
+ currency: "USD",
+ value: "-1.00",
+});
+
+// Totals
+const validTotal = Object.freeze({
+ label: "Valid Total",
+ amount: validAmount,
+});
+
+const invalidTotal = Object.freeze({
+ label: "Invalid Total",
+ amount: invalidAmount,
+});
+
+const invalidNegativeTotal = Object.freeze({
+ label: "Invalid negative total",
+ amount: negativeAmount,
+});
+
+// PaymentDetailsInit
+const validDetails = Object.freeze({
+ total: validTotal,
+});
+
+const invalidDetailsNegativeTotal = Object.freeze({
+ total: invalidNegativeTotal,
+});
+
+// PaymentOptions
+const validOptions = Object.freeze({
+ requestShipping: true,
+});
+
+// PaymentItem
+const validPaymentItem = Object.freeze({
+ amount: validAmount,
+ label: "Valid payment item",
+});
+
+const invalidPaymentItem = Object.freeze({
+ amount: invalidAmount,
+ label: "Invalid payment item",
+});
+
+// PaymentItem
+const validPaymentItems = Object.freeze([validPaymentItem]);
+const invalidPaymentItems = Object.freeze([invalidPaymentItem]);
+
+// PaymentShippingOption
+const invalidShippingOption = Object.freeze({
+ id: "abc",
+ label: "Invalid shipping option",
+ amount: invalidAmount,
+ selected: true,
+});
+
+// PaymentShippingOptions
+const validShippingOption = Object.freeze({
+ id: "abc",
+ label: "valid shipping option",
+ amount: validAmount,
+});
+
+const validShippingOptions = Object.freeze([validShippingOption]);
+const invalidShippingOptions = Object.freeze([invalidShippingOption]);
+
+// PaymentDetailsModifier
+const validModifier = Object.freeze({
+ additionalDisplayItems: validPaymentItems,
+ supportedMethods: "valid-but-wont-ever-match",
+ total: validTotal,
+});
+
+const modifierWithInvalidDisplayItems = Object.freeze({
+ additionalDisplayItems: invalidPaymentItems,
+ supportedMethods: "basic-card",
+ total: validTotal,
+});
+
+const modifierWithValidDisplayItems = Object.freeze({
+ additionalDisplayItems: validPaymentItems,
+ supportedMethods: "basic-card",
+ total: validTotal,
+});
+
+const modifierWithInvalidTotal = Object.freeze({
+ additionalDisplayItems: validPaymentItems,
+ supportedMethods: "basic-card",
+ total: invalidTotal,
+});
+
+const recursiveData = {};
+recursiveData.foo = recursiveData;
+Object.freeze(recursiveData);
+
+const modifierWithRecursiveData = Object.freeze({
+ supportedMethods: "basic-card",
+ total: validTotal,
+ data: recursiveData,
+});
+
+function testBadUpdate(button, badDetails, expectedError, errorCode) {
+ button.disabled = true;
+ promise_test(async t => {
+ const request = new PaymentRequest(
+ validMethods,
+ validDetails,
+ validOptions
+ );
+ request.onshippingaddresschange = event => {
+ event.updateWith(badDetails);
+ };
+ // First we check the bad update.
+ const acceptPromise = request.show();
+ let test_func;
+ if (typeof expectedError == "function") {
+ test_func = promise_rejects_js;
+ } else {
+ test_func = promise_rejects_dom;
+ }
+ await test_func(
+ t,
+ expectedError,
+ acceptPromise,
+ "badDetails must cause acceptPromise to reject with expectedError"
+ );
+ // The request [[state]] is now "closed", so let's check for InvalidStateError
+ await promise_rejects_dom(
+ t,
+ "InvalidStateError",
+ request.show(),
+ "show() must reject with InvalidStateError"
+ );
+ }, button.innerText.trim());
+}
+</script>
+<h2>updateWith() method - "abort the update"</h2>
+<p>
+ Click on each button in sequence from top to bottom without refreshing the page.
+ Each button will bring up the Payment Request UI window.
+</p>
+<p>
+ When the payment sheet is shown, change the shipping address.
+</p>
+<ol>
+ <li>
+ <button onclick="
+ const rejectedPromise = Promise.reject(new SyntaxError('test'));
+ testBadUpdate(this, rejectedPromise, 'AbortError');
+ ">
+ Rejection of detailsPromise must abort the update with an "AbortError" DOMException.
+ </button>
+ </li>
+ <li>
+ <button onclick="
+ const invalidDetails = { total: `this will cause a TypeError!` };
+ testBadUpdate(this, invalidDetails, TypeError);
+ ">
+ Total in the update is a string, so converting to IDL must abort the update with a TypeError.
+ </button>
+ </li>
+ <li>
+ <button onclick="
+ const invalidDetails = { total: recursiveData };
+ testBadUpdate(this, invalidDetails, TypeError);
+ ">
+ Total is recursive, so converting to IDL must abort the update with a TypeError.
+ </button>
+ </li>
+ <li>
+ <button onclick="
+ testBadUpdate(this, invalidDetailsNegativeTotal, TypeError);
+ ">
+ Updating with a negative total results in a TypeError.
+ </button>
+ </li>
+ <li>
+ <button onclick="
+ const badDetails = Object.assign({}, validDetails, { displayItems: invalidPaymentItems });
+ testBadUpdate(this, badDetails, RangeError);
+ ">
+ Updating with a displayItem with an invalid currency results in RangeError.
+ </button>
+ </li>
+ <li>
+ <button onclick="
+ const duplicateShippingOptions = [validShippingOption, validShippingOption];
+ const badDetails = Object.assign({}, validDetails, { shippingOptions: duplicateShippingOptions });
+ testBadUpdate(this, badDetails, TypeError);
+ ">
+ Updating with duplicate shippingOptions (same IDs) results in a TypeError.
+ </button>
+ </li>
+ <li>
+ <button onclick="
+ const badDetails = Object.assign({}, validDetails, { shippingOptions: invalidShippingOptions });
+ testBadUpdate(this, badDetails, RangeError);
+ ">
+ Updating with a shippingOption with an invalid currency value results in a RangError.
+ </button>
+ </li>
+ <li>
+ <button onclick="
+ // validModifier is there as to avoid false positives - it should just get ignored
+ const badModifiers = { modifiers: [ modifierWithInvalidTotal, validModifier ] };
+ const badDetails = Object.assign({}, validDetails, badModifiers);
+ testBadUpdate(this, badDetails, RangeError);
+ ">
+ Must throw a RangeError when a modifier's total item has an invalid currency.
+ </button>
+ </li>
+ <li>
+ <button onclick="
+ // validModifier is there as to avoid false positives - it should just get ignored
+ const badModifiers = { modifiers: [ modifierWithInvalidDisplayItems, validModifier ] };
+ const badDetails = Object.assign({}, validDetails, badModifiers);
+ testBadUpdate(this, badDetails, RangeError);
+ ">
+ Must throw a RangeError when a modifier display item has an invalid currency.
+ </button>
+ </li>
+ <li>
+ <button onclick="
+ // validModifier is there as to avoid false positives - it should just get ignored
+ const badModifiers = { modifiers: [ modifierWithRecursiveData, validModifier ] };
+ const badDetails = Object.assign({}, validDetails, badModifiers);
+ testBadUpdate(this, badDetails, TypeError);
+ ">
+ Must throw as Modifier has a recursive dictionary.
+ </button>
+ </li>
+ <li>
+ <button onclick="done();">Done!</button>
+ </li>
+</ol>
+<small>
+ If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a>
+ and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>.
+</small>
diff --git a/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-state-checks-manual.https.html b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-state-checks-manual.https.html
new file mode 100644
index 0000000000..fb16de5699
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updateWith-state-checks-manual.https.html
@@ -0,0 +1,125 @@
+<!doctype html>
+<meta charset="utf8">
+<link rel="help" href="https://www.w3.org/TR/payment-request/#updatewith()-method">
+<title>updateWith() method - state machine checks</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({ explicit_done: true, explicit_timeout: true });
+const applePay = Object.freeze({
+ supportedMethods: "https://apple.com/apple-pay",
+ data: {
+ version: 3,
+ merchantIdentifier: "merchant.com.example",
+ countryCode: "US",
+ merchantCapabilities: ["supports3DS"],
+ supportedNetworks: ["visa"],
+ }
+});
+const validMethod = Object.freeze({ supportedMethods: "basic-card" });
+const validMethods = Object.freeze([validMethod, applePay]);
+const validAmount = Object.freeze({ currency: "USD", value: "5.00" });
+const validTotal = Object.freeze({
+ label: "label",
+ amount: validAmount,
+});
+const validShippingOption = Object.freeze({
+ id: "a-shipping-option",
+ label: "A shipping option",
+ amount: validAmount,
+ selected: true,
+});
+const validDetails = Object.freeze({
+ total: validTotal,
+ shippingOptions: [validShippingOption],
+});
+const validOptions = Object.freeze({
+ requestShipping: true,
+});
+
+function getPaymentPromises() {
+ const request = new PaymentRequest(validMethods, validDetails, validOptions);
+ const eventPromise = new Promise(resolve => {
+ request.addEventListener("shippingaddresschange", resolve);
+ });
+ const responsePromise = request.show();
+ return { eventPromise, responsePromise };
+}
+
+function testRequestIsClosed(button) {
+ button.disabled = "true";
+ promise_test(async t => {
+ const { eventPromise, responsePromise } = getPaymentPromises();
+ const event = await eventPromise;
+ // We are going to abort the responsePromise, so we can ignore error.
+ responsePromise.catch(err => err);
+ // Set request.[[state]] to closed
+ await event.target.abort();
+ assert_throws_dom(
+ "InvalidStateError",
+ () => {
+ event.updateWith(validDetails);
+ },
+ "request.[[state]] is not interactive, must throw an InvalidStateError."
+ );
+ responsePromise.catch(err => err);
+ }, button.textContent.trim());
+}
+
+function testRequestIsUpdating(button) {
+ button.disabled = "true";
+ promise_test(async t => {
+ const { eventPromise, responsePromise } = getPaymentPromises();
+ const event = await eventPromise;
+ // We are going to put a promise into a pending state
+ // check that a second call to updateWith() throws,
+ // then resolve the pending promise below.
+ let resolver;
+ const pendingPromise = new Promise(resolve => {
+ resolver = resolve;
+ });
+ // Set request.[[updating]] to true
+ event.updateWith(pendingPromise);
+ assert_throws_dom(
+ "InvalidStateError",
+ () => {
+ event.updateWith(validDetails);
+ },
+ "request.[[updating]] to true, must throw an InvalidStateError."
+ );
+ // We got the error we wanted, so let's resolve with valid details.
+ resolver(validDetails);
+ await pendingPromise;
+ await event.target.abort();
+ responsePromise.catch(err => err);
+ }, button.textContent.trim());
+}
+
+</script>
+<h2>updateWith() method - state machine checks</h2>
+<p>
+ Click on each button in sequence from top to bottom without refreshing the page.
+ Each button will bring up the Payment Request UI window.
+</p>
+<p>
+ When the payment sheet is shown, select a different shipping address once. Then pay.
+</p>
+<ol>
+ <li id="test-0">
+ <button onclick="testRequestIsClosed(this);">
+ When updateWith() is called, if request.[[state]] is not "interactive", then throw an " InvalidStateError" DOMException.
+ </button>
+ </li>
+ <li id="test-1">
+ <button onclick="testRequestIsUpdating(this);">
+ When updateWith() is called, If request.[[updating]] is true, then throw an "InvalidStateError" DOMException.
+ </button>
+ </li>
+ <li>
+ <button onclick="done();">Done!</button>
+ </li>
+</ol>
+<small>
+ If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a>
+ and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>.
+</small>
diff --git a/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updatewith-method.https.html b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updatewith-method.https.html
index 9a60fe7a4c..fffd3b3ec5 100644
--- a/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updatewith-method.https.html
+++ b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updatewith-method.https.html
@@ -29,7 +29,9 @@ test(() => {
// Github issue: https://github.com/w3c/browser-payment-api/issues/546
test(() => {
const untrustedEvents = [
- new PaymentRequestUpdateEvent("just a test")
+ new PaymentRequestUpdateEvent("just a test"),
+ new PaymentRequestUpdateEvent("shippingaddresschange"),
+ new PaymentRequestUpdateEvent("shippingoptionchange"),
].forEach(ev => {
assert_throws_dom(
"InvalidStateError",
@@ -45,7 +47,9 @@ test(() => {
test(() => {
const request = new PaymentRequest(defaultMethods, defaultDetails);
const untrustedEvents = [
- new PaymentRequestUpdateEvent("just a test")
+ new PaymentRequestUpdateEvent("just a test"),
+ new PaymentRequestUpdateEvent("shippingaddresschange"),
+ new PaymentRequestUpdateEvent("shippingoptionchange"),
].map(ev => {
request.dispatchEvent(ev); // set .target and dispatch flag
// unstrusted event.
diff --git a/testing/web-platform/tests/payment-request/PaymentValidationErrors/retry-shows-shippingAddress-member-manual.https.html b/testing/web-platform/tests/payment-request/PaymentValidationErrors/retry-shows-shippingAddress-member-manual.https.html
new file mode 100644
index 0000000000..94e6fa5105
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/PaymentValidationErrors/retry-shows-shippingAddress-member-manual.https.html
@@ -0,0 +1,103 @@
+<!doctype html>
+<meta charset="utf8">
+<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentvalidationerrors-shippingaddress">
+<title>
+ PaymentValidationErrors' `shippingAddress` member (AddressErrors)
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../payment-response/helpers.js"></script>
+<script>
+function retryShowsShippingAddressMember(button, error) {
+ button.disabled = true;
+ promise_test(async t => {
+ const options = {
+ requestShipping: true,
+ }
+ const { response } = await getPaymentRequestResponse(options);
+ await response.retry({ shippingAddress: error });
+ await response.complete("success");
+ }, button.textContent.trim());
+}
+</script>
+<h2>
+ Manual Test for PaymentValidationErrors' `shippingAddress` member - Please run in order!
+</h2>
+<p>
+ Click on each button in sequence from top to bottom without refreshing the page.
+ Each button will bring up the Payment Request UI window.
+</p>
+<p>
+ When presented with the payment sheet, use any card and select to "Pay".
+ You will be asked to retry the payment and an error should be shown somewhere
+ in the UI. The expected error string is described in each individual test.
+ If you see the error, hit "Pay" again. If you don't see the error,
+ abort the payment request by hitting "esc" - which means that particular test
+ has failed.
+</p>
+<ol>
+ <li>
+ <button onclick="retryShowsShippingAddressMember(this, { addressLine: 'ADDRESSLINE ERROR' });">
+ The payment sheet shows "ADDRESSLINE ERROR" for the shipping address' addressLine.
+ </button>
+ </li>
+ <li>
+ <button onclick="retryShowsShippingAddressMember(this, { city: 'CITY ERROR' });">
+ The payment sheet shows "CITY ERROR" for the shipping address' city.
+ </button>
+ </li>
+ <li>
+ <button onclick="retryShowsShippingAddressMember(this, { country: 'COUNTRY ERROR' });">
+ The payment sheet shows "COUNTRY ERROR" for the shipping address' country.
+ </button>
+ </li>
+ <li>
+ <button onclick="retryShowsShippingAddressMember(this, { dependentLocality: 'DEPENDENTLOCALITY ERROR' });">
+ The payment sheet shows "DEPENDENTLOCALITY ERROR" for the shipping address' dependentLocality.
+ </button>
+ </li>
+ <li>
+ <button onclick="retryShowsShippingAddressMember(this, { organization: 'ORGANIZATION ERROR' });">
+ The payment sheet shows "ORGANIZATION ERROR" for the shipping address' organization.
+ </button>
+ </li>
+ <li>
+ <button onclick="retryShowsShippingAddressMember(this, { phone: 'PHONE ERROR' });">
+ The payment sheet shows "PHONE ERROR" for the shipping address' phone.
+ </button>
+ </li>
+ <li>
+ <button onclick="retryShowsShippingAddressMember(this, { postalCode: 'POSTALCODE ERROR' });">
+ The payment sheet shows "POSTALCODE ERROR" for the shipping address' postal code.
+ </button>
+ </li>
+ <li>
+ <button onclick="retryShowsShippingAddressMember(this, { recipient: 'RECIPIENT ERROR' });">
+ The payment sheet shows "RECIPIENT ERROR" for the shipping address' recipient.
+ </button>
+ </li>
+ <li>
+ <button onclick="retryShowsShippingAddressMember(this, { region: 'REGION ERROR' });">
+ The payment sheet shows "REGION ERROR" for the shipping address' region.
+ </button>
+ </li>
+ <li>
+ <button onclick="retryShowsShippingAddressMember(this, { regionCode: 'REGIONCODE ERROR' });">
+ The payment sheet shows "REGIONCODE ERROR" for the shipping address' region code.
+ </button>
+ </li>
+ <li>
+ <button onclick="retryShowsShippingAddressMember(this, { sortingCode: 'SORTINGCODE ERROR' });">
+ The payment sheet shows "SORTINGCODE ERROR" for the shipping address' sorting code.
+ </button>
+ </li>
+ <li>
+ <button onclick="done();">
+ Done!
+ </button>
+ </li>
+</ol>
+<small>
+ If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a>
+ and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">owners</a>.
+</small>
diff --git a/testing/web-platform/tests/payment-request/algorithms-manual.https.html b/testing/web-platform/tests/payment-request/algorithms-manual.https.html
new file mode 100644
index 0000000000..b90c312aba
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/algorithms-manual.https.html
@@ -0,0 +1,176 @@
+<!doctype html>
+<meta charset="utf8">
+<link rel="help" href="https://w3c.github.io/payment-request/#algorithms">
+<title>
+ Payment Request algorithms
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({
+ explicit_done: true,
+ explicit_timeout: true,
+});
+const methods = [
+ {
+ supportedMethods: "basic-card",
+ },
+ {
+ supportedMethods: "https://apple.com/apple-pay",
+ data: {
+ version: 3,
+ merchantIdentifier: "merchant.com.example",
+ countryCode: "US",
+ merchantCapabilities: ["supports3DS"],
+ supportedNetworks: ["visa"],
+ },
+ }
+];
+const shippingOptions = {
+ shippingOptions: [
+ {
+ id: "fail",
+ label: "Option 1",
+ amount: {
+ currency: "USD",
+ value: "5.00",
+ },
+ selected: true,
+ },
+ {
+ id: "pass",
+ label: "Option 2",
+ amount: {
+ currency: "USD",
+ value: "5.00",
+ },
+ },
+ ],
+};
+
+const detailsNoShippingOptions = {
+ total: {
+ label: "Total due",
+ amount: {
+ currency: "USD",
+ value: "1.0",
+ },
+ },
+};
+
+const detailsWithShippingOptions = Object.assign(
+ {
+ total: {
+ label: "Total due",
+ amount: {
+ currency: "USD",
+ value: "1.0",
+ },
+ },
+ },
+ shippingOptions
+);
+
+const options = {
+ requestShipping: true,
+};
+
+function testFireEvent(button, details, eventName, expectRequestProps) {
+ button.disabled = true;
+ promise_test(async t => {
+ new PaymentRequest(methods, detailsNoShippingOptions, options);
+ const request = new PaymentRequest(methods, details, options);
+ const handlerPromise = new Promise(resolve => {
+ request[`on${eventName}`] = event => {
+ // "prevent immediate propagation" flag is set.
+ // This listener below won't fire!
+ event.updateWith(details);
+ resolve(event);
+ };
+ });
+ // This listener should never fire because the
+ // the event handler caused "prevent immediate propagation" to be set.
+ request.addEventListener(
+ eventName,
+ t.unreached_func("Second event listener should never fire")
+ );
+ const response = await request.show();
+ const event = await handlerPromise;
+ assert_true(
+ event instanceof window.PaymentRequestUpdateEvent,
+ "Expected instances of PaymentRequestUpdateEvent"
+ );
+ await response.complete("success");
+ }, button.textContent.trim());
+}
+
+async function runAbortTest(button) {
+ button.disabled = true;
+ const { textContent: testName } = button;
+ promise_test(async t => {
+ const request = new PaymentRequest(methods, detailsNoShippingOptions);
+ // Await the user to abort
+ await promise_rejects_dom(t, "AbortError", request.show());
+ // [[state]] is now closed
+ await promise_rejects_dom(t, "InvalidStateError", request.show());
+ }, testName.trim());
+}
+</script>
+<h2>
+ Tests for "algorithms" section
+</h2>
+<p>
+ Click on each button in sequence from top to bottom without refreshing the page.
+ Each button will bring up the Payment Request UI window.
+</p>
+<section>
+ <h3 id="abort-algo">
+ User aborts the payment request algorithm
+ </h3>
+ <link rel="help" href="https://w3c.github.io/payment-request/#user-aborts-the-payment-request-algorithm">
+ <p>
+ When presented with the payment sheet, abort the payment request (e.g., by hitting the esc key or pressing a UA provided button).
+ </p>
+ <ol>
+ <li>
+ <button onclick="runAbortTest(this);">
+ If the user aborts, the UA must run the user aborts the payment request algorithm.
+ </button>
+ </li>
+ </ol>
+</section>
+
+<section>
+ <h3 id="shipping-address-changed-algo">Shipping address changed algorithm</h3>
+ <link rel="help" href="https://www.w3.org/TR/payment-request/#shipping-address-changed-algorithm">
+ <p>
+ When prompted, please change or enter a new shipping address and then select Pay.
+ </p>
+ <ol>
+ <li>
+ <button onclick="testFireEvent(this, detailsWithShippingOptions, 'shippingaddresschange', {});">
+ The shipping address changed algorithm runs when the user provides a new shipping address.
+ </button>
+ </li>
+ </ol>
+</section>
+
+<section>
+ <h3 id="shipping-option-changed-algo">Shipping option changed algorithm</h3>
+ <link rel="help" href="https://w3c.github.io/payment-request/#shipping-option-changed-algorithm">
+ <p>
+ Finally, when prompted, please select "shipping option 2" and then select Pay.
+ </p>
+ <ol>
+ <li>
+ <button onclick="testFireEvent(this, detailsWithShippingOptions, 'shippingoptionchange', {}, 'pass'); done();">
+ The shipping option changed algorithm runs when the user chooses a new shipping option.
+ </button>
+ </li>
+ </ol>
+</section>
+
+<small>
+ If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a>
+ and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>.
+</small>
diff --git a/testing/web-platform/tests/payment-request/billing-address-changed-manual.https.html b/testing/web-platform/tests/payment-request/billing-address-changed-manual.https.html
new file mode 100644
index 0000000000..d03f761518
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/billing-address-changed-manual.https.html
@@ -0,0 +1,115 @@
+<!DOCTYPE html> <meta charset="utf-8" />
+<title>Test for requesting billing address</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({
+ explicit_done: true,
+ explicit_timeout: true,
+ });
+
+ const methods = [
+ { supportedMethods: "basic-card" },
+ {
+ supportedMethods: "https://apple.com/apple-pay",
+ data: {
+ version: 3,
+ merchantIdentifier: "merchant.com.example",
+ countryCode: "US",
+ merchantCapabilities: ["supports3DS"],
+ supportedNetworks: ["visa"],
+ },
+ },
+ ];
+
+ const details = {
+ total: {
+ label: "label",
+ amount: { currency: "USD", value: "5.00" },
+ },
+ };
+ test(() => {
+ assert_true(
+ "onpaymentmethodchange" in PaymentRequest.prototype,
+ "The paymentmethodchange is not supported"
+ );
+ }, "onpaymentmethodchange is in prototype");
+
+ function dontRequestBillingAddress() {
+ promise_test(async t => {
+ const request = new PaymentRequest(methods, details, {});
+ const showPromise = request.show();
+
+ // Let's check the method data from event.
+ const { methodDetails } = await new Promise(resolve =>
+ request.addEventListener("paymentmethodchange", resolve)
+ );
+
+ assert_true("billingAddress" in methodDetails);
+ assert_equals(
+ methodDetails.billingAddress,
+ null,
+ "Expected methodDetails.billingAddress to be null"
+ );
+ await request.abort();
+ });
+ }
+
+ function requestBillingAddress() {
+ promise_test(async t => {
+ const request = new PaymentRequest(methods, details, {
+ requestBillingAddress: true,
+ });
+ const showPromise = request.show();
+
+ // Let's check the method data from event.
+ const { methodDetails } = await new Promise(resolve =>
+ request.addEventListener("paymentmethodchange", resolve)
+ );
+
+ assert_true("billingAddress" in methodDetails);
+
+ const { billingAddress } = methodDetails;
+ assert_true(
+ billingAddress instanceof ContactAddress,
+ "Expected instance of ContactAddress"
+ );
+ await request.abort();
+ });
+ }
+</script>
+
+<h2>Request billing address</h2>
+<p>
+ Click on each button in sequence from top to bottom without refreshing the
+ page. Each button will bring up the Payment Request UI window.
+</p>
+<p>
+ When the payment sheet is presented, select a payment method (e.g., a credit
+ card).
+</p>
+<ol>
+ <li>
+ <button onclick="dontRequestBillingAddress()">
+ When no billing address is requested,
+ `PaymentMethodChangeEvent.methodDetails.billingAddress` is null.
+ </button>
+ </li>
+ <li>
+ <button onclick="requestBillingAddress()">
+ When billing address is
+ requested,`PaymentMethodChangeEvent.methodDetails.billingAddress` is a
+ `ContactAddress`.
+ </button>
+ </li>
+ <li><button onclick="done()">Done!</button></li>
+</ol>
+<small>
+ If you find a buggy test, please
+ <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a> and
+ tag one of the
+ <a
+ href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml"
+ >suggested reviewers</a
+ >.
+</small>
diff --git a/testing/web-platform/tests/payment-request/change-shipping-option-manual.https.html b/testing/web-platform/tests/payment-request/change-shipping-option-manual.https.html
new file mode 100644
index 0000000000..438001804a
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/change-shipping-option-manual.https.html
@@ -0,0 +1,104 @@
+<!DOCTYPE html>
+<!-- Copyright Β© 2017 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
+<meta charset="utf-8">
+<title>Test for PaymentRequest shippingOption attribute</title>
+<link rel="help" href="https://w3c.github.io/payment-request/#shippingoption-attribute">
+<link rel="help" href="https://w3c.github.io/payment-request/#onshippingoptionchange-attribute">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({ explicit_done: true, explicit_timeout: true });
+const validMethod = Object.freeze({ supportedMethods: "basic-card" });
+const applePayMethod = {
+ supportedMethods: "https://apple.com/apple-pay",
+ data: {
+ version: 3,
+ merchantIdentifier: "merchant.com.example",
+ countryCode: "US",
+ merchantCapabilities: ["supports3DS"],
+ supportedNetworks: ["visa"],
+ },
+};
+const validMethods = Object.freeze([validMethod, applePayMethod]);
+const validAmount = Object.freeze({ currency: "USD", value: "5.00" });
+const validTotal = Object.freeze({
+ label: "label",
+ amount: validAmount,
+});
+const validDetails = Object.freeze({ total: validTotal });
+
+const validShippingOption1 = Object.freeze({
+ id: "valid-1",
+ label: "PICK ME!",
+ amount: validAmount,
+ selected: false,
+});
+
+const validShippingOption2 = Object.freeze({
+ id: "initially-selected",
+ label: "Valid shipping option 2",
+ amount: validAmount,
+ selected: true,
+});
+
+const requestShipping = Object.freeze({
+ requestShipping: true,
+});
+
+function testShippingOptionChanged() {
+ promise_test(async t => {
+ const detailsWithShippingOptions = Object.assign({}, validDetails, {
+ shippingOptions: [validShippingOption1, validShippingOption2],
+ });
+ const request = new PaymentRequest(
+ validMethods,
+ detailsWithShippingOptions,
+ requestShipping
+ );
+ assert_equals(
+ request.shippingOption,
+ "initially-selected",
+ "Must be 'initially-selected', as the selected member is true"
+ );
+ const listenerPromise = new Promise(resolve => {
+ request.addEventListener("shippingoptionchange", () => {
+ resolve(request.shippingOption);
+ });
+ });
+ const handlerPromise = new Promise(resolve => {
+ request.onshippingoptionchange = () => {
+ resolve(request.shippingOption);
+ };
+ });
+ request.show().catch(err => err);
+
+ const results = await Promise.all([listenerPromise, handlerPromise]);
+ assert_true(
+ results.every(result => result === "valid-1"),
+ "Expected valid-1 as the shippingOption"
+ );
+ await request.abort();
+ });
+ done();
+}
+</script>
+
+<h2>PaymentRequest shippingOption attribute</h2>
+<p>
+ Click on each button in sequence from top to bottom without refreshing the page.
+ Each button will bring up the Payment Request UI window.
+</p>
+<p>
+ When the payment sheet is presented, select "PICK ME!" as the shipping option.
+</p>
+<ol>
+ <li>
+ <button onclick="testShippingOptionChanged()">
+ When the shipping option is manually changed, request.shippingOption represents the user's choice.
+ </button>
+ </li>
+</ol>
+<small>
+ If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a>
+ and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>.
+</small>
diff --git a/testing/web-platform/tests/payment-request/change-shipping-option-select-last-manual.https.html b/testing/web-platform/tests/payment-request/change-shipping-option-select-last-manual.https.html
new file mode 100644
index 0000000000..4ad31d6531
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/change-shipping-option-select-last-manual.https.html
@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test for PaymentDetailsBase's shippingOptions member</title>
+<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentdetailsbase-shippingoptions">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({ explicit_done: true, explicit_timeout: true });
+const validMethods = Object.freeze([
+ { supportedMethods: "basic-card" },
+ {
+ supportedMethods: "https://apple.com/apple-pay",
+ data: {
+ version: 3,
+ merchantIdentifier: "merchant.com.example",
+ countryCode: "US",
+ merchantCapabilities: ["supports3DS"],
+ supportedNetworks: ["visa"],
+ },
+ },
+]);
+const validAmount = Object.freeze({ currency: "USD", value: "5.00" });
+const validTotal = Object.freeze({
+ label: "label",
+ amount: validAmount,
+});
+const validDetails = Object.freeze({ total: validTotal });
+
+const validShippingOption1 = Object.freeze({
+ id: "fail-if-selected-1",
+ label: "FAIL if selected 1",
+ amount: validAmount,
+ selected: true,
+});
+
+const validShippingOption2 = Object.freeze({
+ id: "fail-if-selected-2",
+ label: "FAIL if selected 2",
+ amount: validAmount,
+ selected: false,
+});
+
+const validShippingOption3 = Object.freeze({
+ id: "pass-if-selected",
+ label: "THIS MUST BE AUTOMATICALLY SELECTED",
+ amount: validAmount,
+ selected: true,
+});
+
+function testShippingOptionChanged(button) {
+ button.disabled = true;
+ promise_test(async t => {
+ const detailsWithShippingOptions = {
+ ...validDetails,
+ shippingOptions: [
+ validShippingOption1,
+ validShippingOption2,
+ validShippingOption3,
+ ],
+ };
+ const request = new PaymentRequest(
+ validMethods,
+ detailsWithShippingOptions,
+ { requestShipping: true }
+ );
+ assert_equals(
+ request.shippingOption,
+ "pass-if-selected",
+ "Must be 'pass-if-selected', as the selected member is true"
+ );
+ request.onshippingoptionchange = () => {
+ assert_unreached("onshippingoptionchange fired unexpectedly");
+ };
+ const response = await request.show();
+ assert_equals(response.shippingOption, "pass-if-selected");
+ response.complete();
+ }, button.textContent.trim());
+ done();
+}
+</script>
+
+<h2>PaymentRequest shippingOption attribute</h2>
+<p>
+ Click on each button in sequence from top to bottom without refreshing the page.
+ Each button will bring up the Payment Request UI window.
+</p>
+<p>
+ When the payment sheet is presented, hit pay.
+</p>
+<ol>
+ <li>
+ <button onclick="testShippingOptionChanged(this)">
+ When default shipping option is pre-selected, must not fire onshippingoptionchange
+ and PaymentResponse must reflect the pre-selected option.
+ </button>
+ </li>
+</ol>
+<small>
+ If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a>
+ and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>.
+</small>
diff --git a/testing/web-platform/tests/payment-request/dynamically-change-shipping-options-manual.https.html b/testing/web-platform/tests/payment-request/dynamically-change-shipping-options-manual.https.html
new file mode 100644
index 0000000000..0e6670a1b8
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/dynamically-change-shipping-options-manual.https.html
@@ -0,0 +1,142 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Test for PaymentRequest shippingOption dynamic updating</title>
+<link
+ rel="help"
+ href="https://w3c.github.io/payment-request/#shippingoption-attribute"
+/>
+<link
+ rel="help"
+ href="https://w3c.github.io/payment-request/#onshippingoptionchange-attribute"
+/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup({ explicit_done: true, explicit_timeout: true });
+ const validMethod = Object.freeze({ supportedMethods: "basic-card" });
+ const applePayMethod = {
+ supportedMethods: "https://apple.com/apple-pay",
+ data: {
+ version: 3,
+ merchantIdentifier: "merchant.com.example",
+ countryCode: "US",
+ merchantCapabilities: ["supports3DS"],
+ supportedNetworks: ["visa"],
+ },
+ };
+ const validMethods = Object.freeze([validMethod, applePayMethod]);
+ const validAmount = Object.freeze({ currency: "USD", value: "5.00" });
+ const validTotal = Object.freeze({
+ label: "label",
+ amount: validAmount,
+ });
+ const validDetails = Object.freeze({ total: validTotal });
+
+ const initialValidShippingOption = Object.freeze({
+ id: "default-method",
+ label: "Default shipping method",
+ amount: validAmount,
+ selected: true,
+ });
+
+ const validDynamicShippingOption = Object.freeze({
+ id: "dynamically-added-id",
+ label: "Dynamically added shipping option",
+ amount: validAmount,
+ selected: false,
+ });
+
+ const requestShipping = Object.freeze({
+ requestShipping: true,
+ });
+
+ function testShippingOptionChanged() {
+ promise_test(async (t) => {
+ const detailsWithShippingOptions = {
+ ...validDetails,
+ shippingOptions: [initialValidShippingOption],
+ };
+ const request = new PaymentRequest(
+ validMethods,
+ detailsWithShippingOptions,
+ requestShipping
+ );
+ const shippingAddressChangeListener = new Promise((resolve) => {
+ request.addEventListener(
+ "shippingaddresschange",
+ (ev) => {
+ // resolve(request.shippingOption);
+ ev.updateWith({
+ shippingOptions: [
+ initialValidShippingOption,
+ validDynamicShippingOption,
+ ],
+ });
+ resolve();
+ },
+ { once: true }
+ );
+ });
+ const handlerPromise = new Promise((resolve) => {
+ request.onshippingoptionchange = () => {
+ resolve(request.shippingOption);
+ };
+ });
+ request.show().catch((err) => err);
+
+ const results = await Promise.all([
+ shippingAddressChangeListener,
+ handlerPromise,
+ ]);
+ assert_true(
+ results[1] === "dynamically-added-id",
+ "Expected dynamically-added-id as the shippingOption"
+ );
+ await request.abort();
+ });
+ }
+</script>
+
+<h2>PaymentRequest shippingOption attribute</h2>
+<p>
+ Click on each button in sequence from top to bottom without refreshing the
+ page. Each button (except the 'Done' button) will bring up the Payment Request
+ UI window.
+</p>
+<ol>
+ <li>
+ When the payment sheet is presented, view options for Shipping Method. There
+ should only be one: "Default shipping method"
+ </li>
+ <li>
+ Change your Shipping Address - either update your existing one by changing
+ something (name, address, etc), or select a different Shipping Address, or
+ add a new Shipping Address and select it.
+ </li>
+ <li>
+ Go back to Shipping Method, and there is now an option called "Dynamically
+ added shipping option". Select it
+ </li>
+ <li>
+ Click on the 'Done' button
+ </li>
+</ol>
+<ul>
+ <li>
+ <button onclick="testShippingOptionChanged()">
+ When the address is changed, shipping methods can be updated
+ </button>
+ </li>
+ <li>
+ <button onclick="done()">Done</button>
+ </li>
+</ul>
+<small>
+ If you find a buggy test, please
+ <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a> and
+ tag one of the
+ <a
+ href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml"
+ >suggested reviewers</a
+ >.
+</small>
diff --git a/testing/web-platform/tests/payment-request/historical.https.html b/testing/web-platform/tests/payment-request/historical.https.html
index aa183a58cd..3e881d0122 100644
--- a/testing/web-platform/tests/payment-request/historical.https.html
+++ b/testing/web-platform/tests/payment-request/historical.https.html
@@ -10,7 +10,7 @@
["paymentRequestID", "PaymentResponse"],
// https://github.com/w3c/browser-payment-api/pull/258
- ["careOf", "PaymentAddress"],
+ ["careOf", "ContactAddress"],
// https://github.com/w3c/browser-payment-api/pull/219
["totalAmount", "PaymentResponse"],
@@ -20,7 +20,7 @@
["paymentRequestId", "PaymentResponse"],
// https://github.com/w3c/payment-request/pull/765
- ["languageCode", "PaymentAddress"],
+ ["languageCode", "ContactAddress"],
//https://github.com/whatwg/html/pull/5915
["allowPaymentRequest", "HTMLIFrameElement"],
diff --git a/testing/web-platform/tests/payment-request/payment-request-constructor-thcrash.https.html b/testing/web-platform/tests/payment-request/payment-request-constructor-thcrash.https.html
new file mode 100644
index 0000000000..b600307085
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/payment-request-constructor-thcrash.https.html
@@ -0,0 +1,254 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Crash tests PaymentRequest Constructor</title>
+<link rel="help" href="https://w3c.github.io/browser-payment-api/#constructor">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+"use strict";
+const ABUSIVE_AMOUNT = 100000;
+
+const applePay = {
+ supportedMethods: "https://apple.com/apple-pay",
+ data: {
+ version: 3,
+ merchantIdentifier: "merchant.com.example",
+ countryCode: "US",
+ merchantCapabilities: ["supports3DS"],
+ supportedNetworks: ["visa"],
+ }
+};
+
+const basicCard = Object.freeze({
+ supportedMethods: "basic-card",
+});
+
+const defaultAmount = Object.freeze({
+ currency: "USD",
+ value: "1.00",
+});
+
+const evilAmount = Object.freeze({
+ currency: "USD",
+ value: "1".repeat(ABUSIVE_AMOUNT),
+});
+
+const defaultMethods = Object.freeze([basicCard, applePay]);
+
+const defaultTotal = Object.freeze({
+ label: "label",
+ amount: defaultAmount,
+});
+
+const evilTotal = Object.freeze({
+ label: "a".repeat(ABUSIVE_AMOUNT),
+ amount: evilAmount,
+});
+
+const defaultDetails = Object.freeze({
+ total: defaultTotal,
+ get id() {
+ return Math.random();
+ },
+});
+
+const defaultPaymentItem = Object.freeze({
+ label: "label",
+ amount: defaultAmount,
+});
+
+const defaultShippingOption = {
+ get id() {
+ return "shipping option " + Math.random();
+ },
+ amount: defaultAmount,
+ label: "shipping option label",
+};
+
+// First argument is sequence<PaymentMethodData> methodData
+test(() => {
+ let evilMethods = [Object.assign({}, basicCard)];
+ // smoke test
+ try {
+ new PaymentRequest(evilMethods, defaultDetails);
+ } catch (err) {
+ assert_unreached("failed smoke test: " + err.stack);
+ }
+ // Now, let's add an abusive amount of methods.
+ while (evilMethods.length < ABUSIVE_AMOUNT) {
+ evilMethods.push({supportedMethods: "evil-method" + evilMethods.length});
+ }
+ try {
+ new PaymentRequest(evilMethods, defaultDetails);
+ } catch (err) {
+ assert_equals(err.name, "TypeError", "must be a TypeError");
+ }
+}, "Don't crash if there is an abusive number of payment methods in the methodData sequence");
+
+// PaymentMethodData.supportedMethods
+test(() => {
+ const supportedMethods = "basic-card";
+ // Smoke test
+ try {
+ new PaymentRequest([{ supportedMethods }], defaultDetails);
+ } catch (err) {
+ assert_unreached("failed smoke test: " + err.stack);
+ }
+ // Now, we make supportedMethods super large
+ const evilMethodData = [
+ {
+ supportedMethods: supportedMethods.repeat(ABUSIVE_AMOUNT),
+ },
+ ];
+ try {
+ new PaymentRequest(evilMethodData, defaultDetails);
+ } catch (err) {
+ assert_equals(err.name, "TypeError", "must be a TypeError");
+ }
+}, "Don't crash if PaymentMethodData.supportedMethods is an abusive length");
+
+// PaymentDetailsInit.id
+test(() => {
+ const id = "abc";
+ // Smoke Test
+ try {
+ new PaymentRequest(
+ defaultMethods,
+ Object.assign({}, defaultDetails, { id })
+ );
+ } catch (err) {
+ assert_unreached("failed smoke test: " + err.stack);
+ }
+ // Now, we make the id super large;
+ const evilDetails = Object.assign({}, defaultDetails, {
+ id: id.repeat(ABUSIVE_AMOUNT),
+ });
+ try {
+ new PaymentRequest(defaultMethods, evilDetails);
+ } catch (err) {
+ assert_equals(err.name, "TypeError", "must be a TypeError");
+ }
+}, "Don't crash if the request id has an abusive length");
+
+// PaymentDetailsInit.total.label
+test(() => {
+ const evilDetails = Object.assign({}, defaultDetails);
+ // Smoke Test
+ try {
+ new PaymentRequest(defaultMethods, evilDetails);
+ } catch (err) {
+ assert_unreached("failed smoke test: " + err.stack);
+ }
+ // Now, we make the label super large;
+ evilDetails.total = {
+ label: "l".repeat(ABUSIVE_AMOUNT),
+ amount: defaultAmount,
+ };
+ try {
+ new PaymentRequest(defaultMethods, evilDetails);
+ } catch (err) {
+ assert_equals(err.name, "TypeError", "must be a TypeError");
+ }
+}, "Don't crash if PaymentDetailsInit.total.label is an abusive length");
+
+test(() => {
+ const evilDetails = Object.assign({}, defaultDetails);
+ // Smoke Test
+ try {
+ new PaymentRequest(defaultMethods, evilDetails);
+ } catch (err) {
+ assert_unreached("failed smoke test: " + err.stack);
+ }
+ // Now, we can use evilAmount
+ evilDetails.total = evilAmount;
+ try {
+ new PaymentRequest(defaultMethods, evilDetails);
+ } catch (err) {
+ assert_equals(err.name, "TypeError", "must be a TypeError");
+ }
+}, "Don't crash if total.amount.value is an abusive length");
+
+for (const [prop, defaultValue] of [
+ ["displayItems", defaultPaymentItem],
+ ["shippingOptions", defaultShippingOption],
+]) {
+ test(() => {
+ const evilDetails = Object.assign({}, defaultDetails);
+ evilDetails[prop] = [defaultValue];
+ // Smoke Test
+ try {
+ new PaymentRequest(defaultMethods, evilDetails);
+ } catch (err) {
+ assert_unreached("failed smoke test: " + err.stack);
+ }
+ while (evilDetails[prop].length < ABUSIVE_AMOUNT) {
+ evilDetails[prop] = evilDetails[prop].concat(evilDetails[prop]);
+ }
+ // Now, construct with evil items!
+ try {
+ new PaymentRequest(defaultMethods, evilDetails);
+ } catch (err) {
+ assert_equals(err.name, "TypeError", "must be a TypeError");
+ }
+ }, `Don't crash if details.${prop} has an abusive number of items`);
+}
+
+test(() => {
+ const evilDetails = Object.assign({}, defaultDetails);
+ const evilShippingOption = Object.assign({}, defaultShippingOption);
+ evilDetails.shippingOptions = [evilShippingOption];
+ // Smoke Test
+ try {
+ new PaymentRequest(defaultMethods, evilDetails);
+ } catch (err) {
+ assert_unreached("failed smoke test: " + err.stack);
+ }
+ // Now, we make the label super large;
+ evilShippingOption.label = "l".repeat(ABUSIVE_AMOUNT);
+ try {
+ new PaymentRequest(defaultMethods, evilDetails);
+ } catch (err) {
+ assert_equals(err.name, "TypeError", "must be a TypeError");
+ }
+}, "Don't crash if PaymentShippingOptions.label is an abusive length");
+
+test(() => {
+ const evilDetails = Object.assign({}, defaultDetails);
+ const evilShippingOption = Object.assign({}, defaultShippingOption);
+ evilDetails.shippingOptions = [evilShippingOption];
+ // Smoke Test
+ try {
+ new PaymentRequest(defaultMethods, evilDetails);
+ } catch (err) {
+ assert_unreached("failed smoke test: " + err.stack);
+ }
+ // Now, we make use of evilAmount;
+ evilShippingOption.amount = evilAmount;
+ try {
+ new PaymentRequest(defaultMethods, evilDetails);
+ } catch (err) {
+ assert_equals(err.name, "TypeError", "must be a TypeError");
+ }
+}, "Don't crash if the PaymentShippingOptions.amount.value is an abusive length");
+
+test(() => {
+ const evilDetails = Object.assign({}, defaultDetails);
+ const evilDisplayItem = Object.assign({}, defaultPaymentItem);
+ evilDetails.displayItems = [evilDisplayItem];
+ // Smoke Test
+ try {
+ new PaymentRequest(defaultMethods, evilDetails);
+ } catch (err) {
+ assert_unreached("failed smoke test: " + err.stack);
+ }
+ // Now, we make the label super large;
+ evilDisplayItem.label = "l".repeat(ABUSIVE_AMOUNT);
+ try {
+ new PaymentRequest(defaultMethods, evilDetails);
+ } catch (err) {
+ assert_equals(err.name, "TypeError", "must be a TypeError");
+ }
+}, "Don't crash if PaymentItem.label is an abusive length");
+</script>
diff --git a/testing/web-platform/tests/payment-request/payment-request-constructor.https.sub.html b/testing/web-platform/tests/payment-request/payment-request-constructor.https.sub.html
index c1ecc22583..9b0ad06454 100644
--- a/testing/web-platform/tests/payment-request/payment-request-constructor.https.sub.html
+++ b/testing/web-platform/tests/payment-request/payment-request-constructor.https.sub.html
@@ -240,7 +240,7 @@ test(() => {
test(() => {
smokeTest();
- for (const prop in ["displayItems", "modifiers"]) {
+ for (const prop in ["displayItems", "shippingOptions", "modifiers"]) {
try {
const details = Object.assign({}, defaultDetails, { [prop]: [] });
new PaymentRequest(defaultMethods, details);
@@ -361,6 +361,186 @@ test(() => {
}
}, "it handles high precision currency values without throwing");
+// Process shipping options:
+
+const defaultShippingOption = Object.freeze({
+ id: "default",
+ label: "",
+ amount: defaultAmount,
+ selected: false,
+});
+const defaultShippingOptions = Object.freeze([
+ Object.assign({}, defaultShippingOption),
+]);
+
+test(() => {
+ smokeTest();
+ for (const amount of invalidAmounts) {
+ const invalidAmount = Object.assign({}, defaultAmount, {
+ value: amount,
+ });
+ const invalidShippingOption = Object.assign({}, defaultShippingOption, {
+ amount: invalidAmount,
+ });
+ const details = Object.assign({}, defaultDetails, {
+ shippingOptions: [invalidShippingOption],
+ });
+ assert_throws_js(
+ TypeError,
+ () => {
+ new PaymentRequest(defaultMethods, details, { requestShipping: true });
+ },
+ `Expected TypeError for option.amount.value: "${amount}"`
+ );
+ }
+}, `For each option in details.shippingOptions: if option.amount.value is not a valid decimal monetary value, then throw a TypeError`);
+
+test(() => {
+ smokeTest();
+ const shippingOptions = [defaultShippingOption];
+ const details = Object.assign({}, defaultDetails, { shippingOptions });
+ const request = new PaymentRequest(defaultMethods, details);
+ assert_equals(
+ request.shippingOption,
+ null,
+ "shippingOption must be null, as requestShipping is missing"
+ );
+ // defaultDetails lacks shipping options
+ const request2 = new PaymentRequest(defaultMethods, defaultDetails, {
+ requestShipping: true,
+ });
+ assert_equals(
+ request2.shippingOption,
+ null,
+ `request2.shippingOption must be null`
+ );
+}, "If there is no selected shipping option, then PaymentRequest.shippingOption remains null");
+
+test(() => {
+ smokeTest();
+ const selectedOption = Object.assign({}, defaultShippingOption, {
+ selected: true,
+ id: "the-id",
+ });
+ const shippingOptions = [selectedOption];
+ const details = Object.assign({}, defaultDetails, { shippingOptions });
+ const requestNoShippingRequested1 = new PaymentRequest(
+ defaultMethods,
+ details
+ );
+ assert_equals(
+ requestNoShippingRequested1.shippingOption,
+ null,
+ "Must be null when no shipping is requested (defaults to false)"
+ );
+ const requestNoShippingRequested2 = new PaymentRequest(
+ defaultMethods,
+ details,
+ { requestShipping: false }
+ );
+ assert_equals(
+ requestNoShippingRequested2.shippingOption,
+ null,
+ "Must be null when requestShipping is false"
+ );
+ const requestWithShipping = new PaymentRequest(defaultMethods, details, {
+ requestShipping: "truthy value",
+ });
+ assert_equals(
+ requestWithShipping.shippingOption,
+ "the-id",
+ "Selected option must be 'the-id'"
+ );
+}, "If there is a selected shipping option, and requestShipping is set, then that option becomes synchronously selected");
+
+test(() => {
+ smokeTest();
+ const failOption1 = Object.assign({}, defaultShippingOption, {
+ selected: true,
+ id: "FAIL1",
+ });
+ const failOption2 = Object.assign({}, defaultShippingOption, {
+ selected: false,
+ id: "FAIL2",
+ });
+ const passOption = Object.assign({}, defaultShippingOption, {
+ selected: true,
+ id: "the-id",
+ });
+ const shippingOptions = [failOption1, failOption2, passOption];
+ const details = Object.assign({}, defaultDetails, { shippingOptions });
+ const requestNoShipping = new PaymentRequest(defaultMethods, details, {
+ requestShipping: false,
+ });
+ assert_equals(
+ requestNoShipping.shippingOption,
+ null,
+ "shippingOption must be null, as requestShipping is false"
+ );
+ const requestWithShipping = new PaymentRequest(defaultMethods, details, {
+ requestShipping: true,
+ });
+ assert_equals(
+ requestWithShipping.shippingOption,
+ "the-id",
+ "selected option must 'the-id"
+ );
+}, "If requestShipping is set, and if there is a multiple selected shipping options, only the last is selected.");
+
+test(() => {
+ smokeTest();
+ const selectedOption = Object.assign({}, defaultShippingOption, {
+ selected: true,
+ });
+ const unselectedOption = Object.assign({}, defaultShippingOption, {
+ selected: false,
+ });
+ const shippingOptions = [selectedOption, unselectedOption];
+ const details = Object.assign({}, defaultDetails, { shippingOptions });
+ const requestNoShipping = new PaymentRequest(defaultMethods, details);
+ assert_equals(
+ requestNoShipping.shippingOption,
+ null,
+ "shippingOption must be null, because requestShipping is false"
+ );
+ assert_throws_js(
+ TypeError,
+ () => {
+ new PaymentRequest(defaultMethods, details, { requestShipping: true });
+ },
+ "Expected to throw a TypeError because duplicate IDs"
+ );
+}, "If there are any duplicate shipping option ids, and shipping is requested, then throw a TypeError");
+
+test(() => {
+ smokeTest();
+ const dupShipping1 = Object.assign({}, defaultShippingOption, {
+ selected: true,
+ id: "DUPLICATE",
+ label: "Fail 1",
+ });
+ const dupShipping2 = Object.assign({}, defaultShippingOption, {
+ selected: false,
+ id: "DUPLICATE",
+ label: "Fail 2",
+ });
+ const shippingOptions = [dupShipping1, defaultShippingOption, dupShipping2];
+ const details = Object.assign({}, defaultDetails, { shippingOptions });
+ const requestNoShipping = new PaymentRequest(defaultMethods, details);
+ assert_equals(
+ requestNoShipping.shippingOption,
+ null,
+ "shippingOption must be null, because requestShipping is false"
+ );
+ assert_throws_js(
+ TypeError,
+ () => {
+ new PaymentRequest(defaultMethods, details, { requestShipping: true });
+ },
+ "Expected to throw a TypeError because duplicate IDs"
+ );
+}, "Throw when there are duplicate shippingOption ids, even if other values are different");
+
// Process payment details modifiers:
test(() => {
smokeTest();
@@ -474,4 +654,44 @@ test(() => {
});
}, "Rethrow any exceptions of JSON-serializing modifier.data");
+//Setting ShippingType attribute during construction
+test(() => {
+ smokeTest();
+ assert_throws_js(TypeError, () => {
+ new PaymentRequest(defaultMethods, defaultDetails, {
+ shippingType: "invalid",
+ });
+ });
+}, "Shipping type should be valid");
+
+test(() => {
+ smokeTest();
+ const request = new PaymentRequest(defaultMethods, defaultDetails, {});
+ assert_equals(request.shippingAddress, null, "must be null");
+}, "PaymentRequest.shippingAddress must initially be null");
+
+test(() => {
+ smokeTest();
+ const request1 = new PaymentRequest(defaultMethods, defaultDetails, {});
+ assert_equals(request1.shippingType, null, "must be null");
+ const request2 = new PaymentRequest(defaultMethods, defaultDetails, {
+ requestShipping: false,
+ });
+ assert_equals(request2.shippingType, null, "must be null");
+}, "If options.requestShipping is not set, then request.shippingType attribute is null.");
+
+test(() => {
+ smokeTest();
+ // option.shippingType defaults to 'shipping'
+ const request1 = new PaymentRequest(defaultMethods, defaultDetails, {
+ requestShipping: true,
+ });
+ assert_equals(request1.shippingType, "shipping", "must be shipping");
+ const request2 = new PaymentRequest(defaultMethods, defaultDetails, {
+ requestShipping: true,
+ shippingType: "delivery",
+ });
+ assert_equals(request2.shippingType, "delivery", "must be delivery");
+}, "If options.requestShipping is true, request.shippingType will be options.shippingType.");
+
</script>
diff --git a/testing/web-platform/tests/payment-request/payment-request-ctor-currency-code-checks.https.sub.html b/testing/web-platform/tests/payment-request/payment-request-ctor-currency-code-checks.https.sub.html
index c608608c7e..b4ca2a0c40 100644
--- a/testing/web-platform/tests/payment-request/payment-request-ctor-currency-code-checks.https.sub.html
+++ b/testing/web-platform/tests/payment-request/payment-request-ctor-currency-code-checks.https.sub.html
@@ -178,6 +178,63 @@ test(() => {
}
}, "Check and canonicalize invalid details.displayItems amount and rethrow RangeError.");
+// Process shipping options:
+test(() => {
+ assert_throws_js(RANGE_ERROR, smokeTest, "Expected smoke test to throw.");
+ const shippingOptions = [];
+ for (const validCurrency of wellFormedCurrencyCodes) {
+ const shippingOption = {
+ id: `test` + Math.random(),
+ label: "shipping option",
+ amount: { currency: validCurrency, value: "5.00" },
+ selected: !shippingOptions.length,
+ };
+ const details = {
+ total: defaultTotal,
+ shippingOptions: [shippingOption],
+ };
+ try {
+ new PaymentRequest(defaultMethods, details, { requestShipping: true });
+ } catch (err) {
+ assert_unreached(
+ `Unexpected exception with valid shippingOption currency code "${validCurrency}": ${err.message}.`
+ );
+ }
+ shippingOptions.push(shippingOption);
+ }
+ try {
+ const details = Object.assign({}, defaultDetails, { shippingOptions });
+ new PaymentRequest(defaultMethods, details, { requestShipping: true });
+ } catch (err) {
+ assert_unreached(
+ `Unexpected error with multiple valid shppingOptions: ${err.message}.`
+ );
+ }
+}, "Check and canonicalize valid details.shippingOptions amount.");
+
+test(() => {
+ assert_throws_js(RANGE_ERROR, smokeTest, "Expected smoke test to throw.");
+ for (const invalidCurrency of invalidCurrencyCodes) {
+ const shippingOption = {
+ id: "test",
+ label: "shipping option",
+ amount: { currency: invalidCurrency, value: "5.00" },
+ selected: true,
+ };
+ const details = {
+ total: defaultTotal,
+ shippingOptions: [shippingOption],
+ };
+ assert_throws_js(
+ RANGE_ERROR,
+ () => {
+ new PaymentRequest(defaultMethods, details, { requestShipping: true });
+ },
+ `Expected RangeError with invalid shippingOption currency code "${invalidCurrency}".`
+ );
+ }
+}, "Check and canonicalize invalid details.shippingOptions amount and rethrow RangeError.");
+
// Process payment details modifiers:
test(() => {
assert_throws_js(RANGE_ERROR, smokeTest, "Expected smoke test to throw.");
diff --git a/testing/web-platform/tests/payment-request/payment-request-onshippingaddresschange-attribute.https.html b/testing/web-platform/tests/payment-request/payment-request-onshippingaddresschange-attribute.https.html
new file mode 100644
index 0000000000..5b2538992f
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/payment-request-onshippingaddresschange-attribute.https.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<!-- Copyright Β© 2017 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
+<meta charset="utf-8">
+<title>Test for onshippingaddresschange attribute</title>
+<link rel="help" href="https://w3c.github.io/browser-payment-api/#onshippingaddresschange-attribute">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+"use strict";
+const applePay = Object.freeze({
+ supportedMethods: "https://apple.com/apple-pay",
+ data: {
+ version: 3,
+ merchantIdentifier: "merchant.com.example",
+ countryCode: "US",
+ merchantCapabilities: ["supports3DS"],
+ supportedNetworks: ["visa"],
+ }
+});
+const basicCard = Object.freeze({ supportedMethods: "basic-card" });
+const defaultMethods = Object.freeze([basicCard, applePay]);
+const defaultDetails = Object.freeze({
+ total: {
+ label: "Total",
+ amount: {
+ currency: "USD",
+ value: "1.00",
+ },
+ },
+});
+
+test(() => {
+ const request = new PaymentRequest(defaultMethods, defaultDetails);
+ assert_idl_attribute(request, "onshippingaddresschange");
+}, "Must have a onshippingaddresschange IDL attribute");
+
+test(() => {
+ const request = new PaymentRequest(defaultMethods, defaultDetails);
+ const ev = new Event("shippingaddresschange");
+ let didHandle = false;
+ request.onshippingaddresschange = evt => {
+ assert_equals(ev, evt, "must be same event");
+ didHandle = true;
+ };
+ request.dispatchEvent(ev);
+ assert_true(didHandle, "event did not fire");
+}, `onshippingaddresschange attribute is a generic handler for "shippingaddresschange"`);
+
+test(() => {
+ const request = new PaymentRequest(defaultMethods, defaultDetails);
+ const ev = new PaymentRequestUpdateEvent("shippingaddresschange");
+ let didHandle = false;
+ request.onshippingaddresschange = evt => {
+ assert_equals(ev, evt, "must be same event");
+ didHandle = true;
+ };
+ request.dispatchEvent(ev);
+ assert_true(didHandle, "event did not fire");
+}, `onshippingaddresschange attribute is a handler for PaymentRequestUpdateEvent`);
+
+test(() => {
+ const request = new PaymentRequest(defaultMethods, defaultDetails);
+ const ev = new PaymentRequestUpdateEvent("shippingaddresschange");
+ let didHandle = false;
+ let didListen = false;
+ request.onshippingaddresschange = evt => {
+ assert_equals(ev, evt, "must be same event");
+ didHandle = true;
+ };
+ request.addEventListener("shippingaddresschange", evt => {
+ assert_equals(ev, evt, "must be same event");
+ didListen = true;
+ });
+ request.dispatchEvent(ev);
+ assert_true(didHandle, "onshippingaddresschange did not receive the event");
+ assert_true(didListen, "addEventListener did not receive the event");
+}, `onshippingaddresschange attribute and listeners both work`);
+</script>
diff --git a/testing/web-platform/tests/payment-request/payment-request-onshippingoptionchange-attribute.https.html b/testing/web-platform/tests/payment-request/payment-request-onshippingoptionchange-attribute.https.html
new file mode 100644
index 0000000000..43ea5dcce8
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/payment-request-onshippingoptionchange-attribute.https.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<!-- Copyright Β© 2017 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
+<meta charset="utf-8">
+<title>Test for onshippingoptionchange attribute</title>
+<link rel="help" href="https://w3c.github.io/browser-payment-api/#onshippingoptionchange-attribute">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+"use strict";
+const applePay = Object.freeze({
+ supportedMethods: "https://apple.com/apple-pay",
+ data: {
+ version: 3,
+ merchantIdentifier: "merchant.com.example",
+ countryCode: "US",
+ merchantCapabilities: ["supports3DS"],
+ supportedNetworks: ["visa"],
+ }
+});
+const basicCard = Object.freeze({ supportedMethods: "basic-card" });
+const defaultMethods = Object.freeze([basicCard, applePay]);
+const defaultDetails = Object.freeze({
+ total: {
+ label: "Total",
+ amount: {
+ currency: "USD",
+ value: "1.00",
+ },
+ },
+});
+
+test(() => {
+ const request = new PaymentRequest(defaultMethods, defaultDetails);
+ assert_idl_attribute(request, "onshippingoptionchange");
+}, "Must have a onshippingoptionchange IDL attribute");
+
+test(() => {
+ const request = new PaymentRequest(defaultMethods, defaultDetails);
+ const ev = new Event("shippingoptionchange");
+ let didHandle = false;
+ request.onshippingoptionchange = evt => {
+ assert_equals(ev, evt, "must be same event");
+ didHandle = true;
+ };
+ request.dispatchEvent(ev);
+ assert_true(didHandle, "event did not fire");
+}, `onshippingoptionchange attribute is a generic handler for "shippingoptionchange"`);
+
+test(() => {
+ const request = new PaymentRequest(defaultMethods, defaultDetails);
+ const ev = new PaymentRequestUpdateEvent("shippingoptionchange");
+ let didHandle = false;
+ request.onshippingoptionchange = evt => {
+ assert_equals(ev, evt, "must be same event");
+ didHandle = true;
+ };
+ request.dispatchEvent(ev);
+ assert_true(didHandle, "event did not fire");
+}, `onshippingoptionchange attribute is a handler for PaymentRequestUpdateEvent`);
+
+test(() => {
+ const request = new PaymentRequest(defaultMethods, defaultDetails);
+ const ev = new PaymentRequestUpdateEvent("shippingoptionchange");
+ let didHandle = false;
+ let didListen = false;
+ request.onshippingoptionchange = evt => {
+ assert_equals(ev, evt, "must be same event");
+ didHandle = true;
+ };
+ request.addEventListener("shippingoptionchange", evt => {
+ assert_equals(ev, evt, "must be same event");
+ didListen = true;
+ });
+ request.dispatchEvent(ev);
+ assert_true(didHandle, "onshippingoptionchange did not receive the event");
+ assert_true(didListen, "addEventListener did not receive the event");
+}, `onshippingoptionchange attribute and listeners both work`);
+
+</script>
diff --git a/testing/web-platform/tests/payment-request/payment-request-shippingAddress-attribute.https.html b/testing/web-platform/tests/payment-request/payment-request-shippingAddress-attribute.https.html
new file mode 100644
index 0000000000..08918356b6
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/payment-request-shippingAddress-attribute.https.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<!-- Copyright Β© 2017 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
+<meta charset="utf-8">
+<title>Test for PaymentRequest shippingAddress attribute</title>
+<link rel="help" href="https://w3c.github.io/payment-request/#shippingaddress-attribute">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+const validMethod = Object.freeze({ supportedMethods: "foo" });
+const validMethods = Object.freeze([validMethod]);
+const validAmount = Object.freeze({ currency: "USD", value: "5.00" });
+const validTotal = Object.freeze({
+ label: "label",
+ amount: validAmount,
+});
+const validDetails = Object.freeze({ total: validTotal });
+
+test(() => {
+ const request = new PaymentRequest(validMethods, validDetails);
+ assert_idl_attribute(request, "shippingAddress");
+}, "Must have a .shippingAddress IDL attribute.");
+
+test(() => {
+ const request = new PaymentRequest(validMethods, validDetails);
+ assert_equals(request.shippingAddress, null, "expected null");
+}, ".shippingAddress attribute must default to null.");
+
+</script>
diff --git a/testing/web-platform/tests/payment-request/payment-request-shippingOption-attribute.https.html b/testing/web-platform/tests/payment-request/payment-request-shippingOption-attribute.https.html
new file mode 100644
index 0000000000..b5f9ea65c6
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/payment-request-shippingOption-attribute.https.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<!-- Copyright Β© 2017 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
+<meta charset="utf-8">
+<title>Test for PaymentRequest shippingOption attribute</title>
+<link rel="help" href="https://w3c.github.io/payment-request/#shippingoption-attribute">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+const validMethod = Object.freeze({ supportedMethods: "foo" });
+const validMethods = Object.freeze([validMethod]);
+const validAmount = Object.freeze({ currency: "USD", value: "5.00" });
+const validTotal = Object.freeze({
+ label: "label",
+ amount: validAmount,
+});
+const validDetails = Object.freeze({ total: validTotal });
+const validShippingOption = Object.freeze({
+ id: "valid",
+ label: "Valid shipping option",
+ amount: validAmount,
+ selected: false,
+});
+
+const requestShipping = Object.freeze({
+ requestShipping: true,
+});
+
+test(() => {
+ const request = new PaymentRequest(validMethods, validDetails);
+ assert_idl_attribute(request, "shippingOption");
+}, "Must have a .shippingOption IDL attribute.");
+
+test(() => {
+ const request = new PaymentRequest(validMethods, validDetails);
+ assert_equals(request.shippingOption, null, "expected null");
+}, ".shippingOption attribute must default to null.");
+
+test(() => {
+ const detailsWithShippingOptions = Object.assign({}, validDetails, {
+ shippingOptions: [validShippingOption],
+ });
+ const request = new PaymentRequest(
+ validMethods,
+ detailsWithShippingOptions,
+ requestShipping
+ );
+ assert_equals(request.shippingOption, null, "expected null");
+}, "If there is a single shipping option, but selected is false, then .shippingOption must be null.");
+
+test(() => {
+ const shippingOption2 = Object.assign({}, validShippingOption, {
+ id: "valid2",
+ });
+ const detailsWithShippingOptions = Object.assign({}, validDetails, {
+ shippingOptions: [validShippingOption, shippingOption2],
+ });
+ const request = new PaymentRequest(
+ validMethods,
+ detailsWithShippingOptions,
+ requestShipping
+ );
+ assert_equals(request.shippingOption, null, "expected null");
+}, "If there are multiple shipping options all with `selected` set to false, then .shippingOption is null.");
+
+test(() => {
+ const shippingOption2 = Object.assign({}, validShippingOption, {
+ id: "pass",
+ selected: true,
+ });
+ const detailsWithShippingOptions = Object.assign({}, validDetails, {
+ shippingOptions: [shippingOption2, validShippingOption],
+ });
+ const request = new PaymentRequest(
+ validMethods,
+ detailsWithShippingOptions,
+ requestShipping
+ );
+ assert_equals(request.shippingOption, "pass", "expected 'pass'");
+}, "Given multiple shipping options, it must use the selected shipping option for .shippingOption value.");
+
+test(() => {
+ const shippingOption1 = Object.assign({}, validShippingOption, {
+ id: "fail",
+ selected: true,
+ });
+ const shippingOption2 = Object.assign({}, validShippingOption, {
+ id: "pass",
+ selected: true,
+ });
+ const detailsWithShippingOptions = Object.assign({}, validDetails, {
+ shippingOptions: [shippingOption1, shippingOption2, validShippingOption],
+ });
+ const request = new PaymentRequest(
+ validMethods,
+ detailsWithShippingOptions,
+ requestShipping
+ );
+ assert_equals(request.shippingOption, "pass", "expected 'pass'");
+}, "If there are multiple of the shipping options with selected true, then .shippingOption is the last selected shipping option in order.");
+</script>
diff --git a/testing/web-platform/tests/payment-request/payment-request-shippingType-attribute.https.html b/testing/web-platform/tests/payment-request/payment-request-shippingType-attribute.https.html
new file mode 100644
index 0000000000..11f75b1c86
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/payment-request-shippingType-attribute.https.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<!-- Copyright Β© 2017 Mozilla and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
+<meta charset="utf-8">
+<title>Test for PaymentRequest's shippingType attribute</title>
+<link rel="help" href="https://w3c.github.io/payment-request/#shippingtype-attribute">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+"use strict";
+const paymentShipingTypes = Object.freeze(["delivery", "pickup", "shipping"]);
+const applePay = Object.freeze({
+ supportedMethods: "https://apple.com/apple-pay",
+ data: {
+ version: 3,
+ merchantIdentifier: "merchant.com.example",
+ countryCode: "US",
+ merchantCapabilities: ["supports3DS"],
+ supportedNetworks: ["visa"],
+ }
+});
+const basicCard = Object.freeze({ supportedMethods: "basic-card" });
+const defaultMethods = Object.freeze([basicCard, applePay]);
+const defaultDetails = Object.freeze({
+ total: {
+ label: "",
+ amount: {
+ currency: "USD",
+ value: "1.00",
+ },
+ },
+});
+
+test(() => {
+ const request = new PaymentRequest(defaultMethods, defaultDetails);
+ assert_idl_attribute(request, "shippingType");
+}, "Must have a shippingType IDL attribute");
+
+test(() => {
+ const request1 = new PaymentRequest(defaultMethods, defaultDetails, {});
+ assert_equals(request1.shippingType, null, "must be null");
+ const request2 = new PaymentRequest(defaultMethods, defaultDetails, {
+ requestShipping: false,
+ });
+ assert_equals(request2.shippingType, null, "must be null");
+ for (const shippingType of paymentShipingTypes) {
+ const request = new PaymentRequest(defaultMethods, defaultDetails, {
+ requestShipping: false,
+ shippingType,
+ });
+ assert_equals(request.shippingType, null, "must be null");
+ }
+}, "If options.requestShipping is false, then request.shippingType attribute is null.");
+
+test(() => {
+ // option.shippingType defaults to 'shipping'
+ const defaultRequest = new PaymentRequest(defaultMethods, defaultDetails, {
+ requestShipping: true,
+ });
+ assert_equals(defaultRequest.shippingType, "shipping", "must be shipping");
+ for (const shippingType of paymentShipingTypes) {
+ const request = new PaymentRequest(defaultMethods, defaultDetails, {
+ requestShipping: true,
+ shippingType,
+ });
+ assert_equals(
+ request.shippingType,
+ shippingType,
+ `must be ${shippingType}`
+ );
+ }
+}, "If options.requestShipping is true, request.shippingType will be options.shippingType.");
+</script>
diff --git a/testing/web-platform/tests/payment-request/payment-response/helpers.js b/testing/web-platform/tests/payment-request/payment-response/helpers.js
index 1242ecb743..3e4f5cfd36 100644
--- a/testing/web-platform/tests/payment-request/payment-response/helpers.js
+++ b/testing/web-platform/tests/payment-request/payment-response/helpers.js
@@ -65,8 +65,30 @@ async function getPaymentRequestResponse(options, id) {
label: "Total due",
amount: { currency: "USD", value: "1.0" },
},
+ shippingOptions: [
+ {
+ id: "fail1",
+ label: "Fail option 1",
+ amount: { currency: "USD", value: "5.00" },
+ selected: false,
+ },
+ {
+ id: "pass",
+ label: "Pass option",
+ amount: { currency: "USD", value: "5.00" },
+ selected: true,
+ },
+ {
+ id: "fail2",
+ label: "Fail option 2",
+ amount: { currency: "USD", value: "5.00" },
+ selected: false,
+ },
+ ],
};
const request = new PaymentRequest(methods, details, options);
+ request.onshippingaddresschange = ev => ev.updateWith(details);
+ request.onshippingoptionchange = ev => ev.updateWith(details);
const response = await request.show();
return { request, response };
}
@@ -106,5 +128,23 @@ async function runManualTest(button, options, expected = {}, id = undefined) {
assert_equals(typeof response.details, "object", "Expected an object");
// Testing that this does not throw:
response.toJSON();
+ if (options && options.requestShipping) {
+ assert_equals(
+ response.shippingOption,
+ "pass",
+ "request.shippingOption must be 'pass'"
+ );
+ } else {
+ assert_equals(
+ request.shippingOption,
+ null,
+ "If requestShipping is falsy, request.shippingOption must be null"
+ );
+ assert_equals(
+ response.shippingOption,
+ null,
+ "request.shippingOption must be null"
+ );
+ }
}, button.textContent.trim());
}
diff --git a/testing/web-platform/tests/payment-request/payment-response/retry-method-manual.https.html b/testing/web-platform/tests/payment-request/payment-response/retry-method-manual.https.html
new file mode 100644
index 0000000000..a5aab49e38
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/payment-response/retry-method-manual.https.html
@@ -0,0 +1,296 @@
+<!doctype html>
+<meta charset="utf8">
+<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentresponse-retry">
+<title>
+ PaymentResponse.prototype.retry() method
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="helpers.js"></script>
+<script>
+test(() => {
+ assert_true(
+ "retry" in PaymentResponse.prototype,
+ "retry must be in prototype"
+ );
+ assert_true(
+ PaymentResponse.prototype.retry instanceof Function,
+ "retry must be a function"
+ );
+}, "PaymentResponse.prototype must have a retry() function (smoke test).");
+
+function checkCompletedCantRetry(button) {
+ button.disabled = true;
+ promise_test(async t => {
+ const { response } = await getPaymentRequestResponse();
+ // sets response.[[complete]] to true.
+ await response.complete("success");
+ return promise_rejects_dom(
+ t,
+ "InvalidStateError",
+ response.retry(),
+ "response.[[complete]] is true, so rejects with InvalidStateError."
+ );
+ }, button.textContent.trim());
+}
+
+function repeatedCallsToRetry(button) {
+ button.disabled = true;
+ promise_test(async t => {
+ const { response } = await getPaymentRequestResponse();
+ const retryPromise = response.retry();
+ await promise_rejects_dom(
+ t,
+ "InvalidStateError",
+ response.retry(),
+ "Calling retry() again rejects with an InvalidStateError"
+ );
+ await retryPromise;
+ await response.complete("success");
+ }, button.textContent.trim());
+}
+
+function callCompleteWhileRetrying(button) {
+ button.disabled = true;
+ promise_test(async t => {
+ const { response } = await getPaymentRequestResponse();
+ const retryPromise = response.retry();
+ const completePromise1 = response.complete("success");
+ const completePromise2 = response.complete("fail");
+ assert_not_equals(
+ completePromise1,
+ completePromise2,
+ "complete() must return unique promises"
+ );
+ await promise_rejects_dom(
+ t,
+ "InvalidStateError",
+ completePromise1,
+ "Calling complete() while retrying rejects with an InvalidStateError"
+ );
+ await promise_rejects_dom(
+ t,
+ "InvalidStateError",
+ completePromise2,
+ "Calling complete() while retrying rejects with an InvalidStateError"
+ );
+ assert_not_equals(
+ completePromise1,
+ completePromise2,
+ "complete() must return unique promises"
+ );
+ await retryPromise;
+ await response.complete("success");
+ }, button.textContent.trim());
+}
+
+function callingRequestAbortMustNotAbort(button) {
+ button.disabled = true;
+ promise_test(async t => {
+ const { response, request } = await getPaymentRequestResponse();
+ const retryPromise = response.retry();
+ await promise_rejects_dom(
+ t,
+ "InvalidStateError",
+ request.abort(),
+ "Calling request.abort() while retrying rejects with an InvalidStateError"
+ );
+ await retryPromise;
+ await response.complete("success");
+ }, button.textContent.trim());
+}
+
+function canRetryMultipleTimes(button) {
+ button.disabled = true;
+ promise_test(async t => {
+ const { response } = await getPaymentRequestResponse();
+ assert_equals(
+ await response.retry(),
+ undefined,
+ "Expected undefined as the resolve value"
+ );
+ assert_equals(
+ await response.retry(),
+ undefined,
+ "Expected undefined as the resolve value"
+ );
+ await response.complete("success");
+ await promise_rejects_dom(
+ t,
+ "InvalidStateError",
+ response.retry(),
+ "Calling retry() after complete() rejects with a InvalidStateError"
+ );
+ }, button.textContent.trim());
+}
+
+function userCanAbortARetry(button) {
+ button.disabled = true;
+ promise_test(async t => {
+ const { response } = await getPaymentRequestResponse();
+ await promise_rejects_dom(
+ t,
+ "AbortError",
+ response.retry(),
+ "The user aborting a retry rejects with a AbortError"
+ );
+ await promise_rejects_dom(
+ t,
+ "InvalidStateError",
+ response.retry(),
+ "After the user aborts, response [[complete]] is true so retry() must reject with InvalidStateError"
+ );
+ await promise_rejects_dom(
+ t,
+ "InvalidStateError",
+ response.complete("success"),
+ "After the user aborts, response [[complete]] is true, so complete() rejects with a InvalidStateError"
+ );
+ }, button.textContent.trim());
+}
+
+function userIsShownErrorsFields(button) {
+ button.disabled = true;
+ promise_test(async t => {
+ const { response, request } = await getPaymentRequestResponse({ requestShipping: true });
+ const retryPromise = response.retry({
+ shippingAddress: { city: "Invalid city", addressLine: "Invalid address line" },
+ });
+ await retryPromise;
+ await response.complete("success");
+ }, button.textContent.trim());
+}
+
+function abortTheUpdate(button) {
+ button.disabled = true;
+ promise_test(async t => {
+ const { response, request } = await getPaymentRequestResponse({
+ requestShipping: true,
+ });
+ const shipOptionChangePromise = new Promise(resolve => {
+ request.onshippingoptionchange = event => {
+ // causes "abort the update" to run
+ event.updateWith({ total: "error!" });
+ resolve();
+ };
+ });
+ const retryPromise = response.retry();
+ await shipOptionChangePromise;
+ await promise_rejects_js(
+ t,
+ TypeError,
+ retryPromise,
+ "retry() aborts with a TypeError, because totals' value is invalid"
+ );
+ await promise_rejects_dom(
+ t,
+ "InvalidStateError",
+ response.complete("success"),
+ "After the user aborts, response [[complete]] is true, so complete() rejects with a InvalidStateError"
+ );
+ }, button.textContent.trim());
+}
+
+function callingRetryReturnsUniquePromise(button){
+ button.disabled = true;
+ promise_test(async t => {
+ const { response } = await getPaymentRequestResponse();
+ const retryPromise = response.retry();
+ const promises = new Set([
+ retryPromise,
+ response.retry(),
+ response.retry(),
+ ]);
+ assert_equals(promises.size, 3, "Must have three unique objects");
+ await retryPromise;
+ await response.complete();
+ }, button.textContent.trim());
+};
+
+
+</script>
+<h2>
+ Manual Tests for PaymentResponse.retry() - Please run in order!
+</h2>
+<p>
+ Click on each button in sequence from top to bottom without refreshing the page.
+ Each button will bring up the Payment Request UI window.
+</p>
+<p>
+ When presented with the payment sheet, use any credit card select to "Pay" multiple times.
+</p>
+<ol>
+ <li>
+ <button onclick="checkCompletedCantRetry(this);">
+ A completed payment request cannot be retried.
+ </button>
+ </li>
+ <li>
+ <button onclick="repeatedCallsToRetry(this);">
+ Calling retry() more than once yield a rejected promise, but the
+ retryPromise resolves independently.
+ </button>
+ </li>
+ <li>
+ <button onclick="callCompleteWhileRetrying(this);">
+ Calling complete() while a retry() is in progress results in an InvalidStateError.
+ </button>
+ </li>
+ <li>
+ <button onclick="callingRequestAbortMustNotAbort(this);">
+ Trying to abort() via PaymentRequest is not possible.
+ </button>
+ </li>
+ <li>
+ <button onclick="canRetryMultipleTimes(this);">
+ It's possible to retry() multiple times and eventually complete().
+ After complete(), however, retry() rejects with an InvalidStateError.
+ </button>
+ </li>
+ <li>
+ <p>
+ When shown the payment sheet, hit pay once, then abort retrying the payment.
+ </p>
+ <button onclick="userCanAbortARetry(this);">
+ The user aborting retrying a payment causes the retryPromise to reject with AbortError.
+ Aborting a payment is causes it complete.
+ </button>
+ </li>
+ <li>
+ <p>
+ When shown the payment sheet, hit pay once. Check payment sheet for error fields.
+ Then hit escape or otherwise abort the payment.
+ </p>
+ <button onclick="userIsShownErrorsFields(this);">
+ When retrying, the user is shown error fields to fix.
+ </button>
+ </li>
+ <li>
+ <p>
+ When shown the payment sheet, hit pay once.
+ Then, change the shipping option.
+ Select to pay again.
+ </p>
+ <button onclick="abortTheUpdate(this);">
+ When "abort the update" occurs because of an update error,
+ the `retryPromise` is rejected and response.[[complete]] becomes true.
+ </button>
+ </li>
+ <li>
+ <p>
+ When shown the payment sheet, hit pay once. Then retry once.
+ </p>
+ <button onclick="callingRetryReturnsUniquePromise(this);">
+ Calling retry() multiple times is always a new object.
+ </button>
+ </li>
+ <li>
+ <button onclick="done();">
+ Done!
+ </button>
+ </li>
+</ol>
+<small>
+ If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a>
+ and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">owners</a>.
+</small>
diff --git a/testing/web-platform/tests/payment-request/payment-response/retry-method-warnings-manual.https.html b/testing/web-platform/tests/payment-request/payment-response/retry-method-warnings-manual.https.html
new file mode 100644
index 0000000000..b68bf18309
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/payment-response/retry-method-warnings-manual.https.html
@@ -0,0 +1,158 @@
+<!DOCTYPE html> <meta charset="utf-8" />
+<title>Warn when errorFields don't match request[[options]]</title>
+<link rel="help" href="https://github.com/w3c/payment-request/pull/807" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="helpers.js"></script>
+<script>
+ test(() => {
+ assert_true(
+ "retry" in PaymentResponse.prototype,
+ "retry must be in prototype"
+ );
+ assert_true(
+ PaymentResponse.prototype.retry instanceof Function,
+ "retry must be a function"
+ );
+ }, "PaymentResponse.prototype must have a retry() function (smoke test).");
+
+ const defaultOptions = {
+ requestPayerName: false,
+ requestPayerEmail: false,
+ requestPayerPhone: false,
+ requestShipping: false,
+ };
+ function testShowWarning(button, errorFields) {
+ button.disabled = true;
+ promise_test(async () => {
+ const { response } = await getPaymentRequestResponse(defaultOptions);
+ await response.retry(errorFields);
+ await response.complete();
+ });
+ }
+</script>
+<h2>Manual Tests - Please run in order!</h2>
+<p>
+ Please open the developer console. Each of the tests below should generate a
+ warning in the developer console.
+</p>
+<p>When presented with the payment sheet, hit pay twice.</p>
+<ol>
+ <li>
+ <button onclick="testShowWarning(this, {payer: {name: 'Dont show this'}});">
+ Show warning if `requestPayerName` if false, and `errorFields.payer.name` is
+ present.
+ </button>
+ </li>
+ <li>
+ <button
+ onclick="testShowWarning(this, {payer: {email: 'Dont show this'}});"
+ >
+ Show warning if `requestPayerEmail` if false, and `errorFields.payer.email`
+ is present.
+ </button>
+ </li>
+ <li>
+ <button
+ onclick="testShowWarning(this, {payer: {phone: 'Dont show this'}});"
+ >
+ Show warning if `requestPayerPhone` if false, and `errorFields.payer.phone`
+ is present.
+ </button>
+ </li>
+ <li>
+ <button onclick="testShowWarning(this, {shippingAddress: {}});">
+ Show warning if `requestShipping` if false, and
+ `errorFields.shippingAddress` member present.
+ </button>
+ </li>
+ <li>
+ <button
+ onclick="testShowWarning(this, {shippingAddress: {addressLine: 'Dont show this'}});"
+ >
+ Show warning if `requestShipping` if false, and
+ `errorFields.shippingAddress.addressLine` member present.
+ </button>
+ </li>
+ <li>
+ <button
+ onclick="testShowWarning(this, {shippingAddress: {city: 'Dont show this'}});"
+ >
+ Show warning if `requestShipping` if false, and
+ `errorFields.shippingAddress.city` member present.
+ </button>
+ </li>
+ <li>
+ <button
+ onclick="testShowWarning(this, {shippingAddress: {country: 'Dont show this'}});"
+ >
+ Show warning if `requestShipping` if false, and
+ `errorFields.shippingAddress.country` member present.
+ </button>
+ </li>
+ <li>
+ <button
+ onclick="testShowWarning(this, {shippingAddress: {dependentLocality: 'Dont show this'}});"
+ >
+ Show warning if `requestShipping` if false, and
+ `errorFields.shippingAddress.dependentLocality` member present.
+ </button>
+ </li>
+ <li>
+ <button
+ onclick="testShowWarning(this, {shippingAddress: {organization: 'Dont show this'}});"
+ >
+ Show warning if `requestShipping` if false, and
+ `errorFields.shippingAddress.organization` member present.
+ </button>
+ </li>
+ <li>
+ <button
+ onclick="testShowWarning(this, {shippingAddress: {phone: 'Dont show this'}});"
+ >
+ Show warning if `requestShipping` if false, and
+ `errorFields.shippingAddress.phone` member present.
+ </button>
+ </li>
+ <li>
+ <button
+ onclick="testShowWarning(this, {shippingAddress: {postalCode: 'Dont show this'}});"
+ >
+ Show warning if `requestShipping` if false, and
+ `errorFields.shippingAddress.postalCode` member present.
+ </button>
+ </li>
+ <li>
+ <button
+ onclick="testShowWarning(this, {shippingAddress: {recipient: 'Dont show this'}});"
+ >
+ Show warning if `requestShipping` if false, and
+ `errorFields.shippingAddress.recipient` member present.
+ </button>
+ </li>
+ <li>
+ <button
+ onclick="testShowWarning(this, {shippingAddress: {region: 'Dont show this'}});"
+ >
+ Show warning if `requestShipping` if false, and
+ `errorFields.shippingAddress.region` member present.
+ </button>
+ </li>
+ <li>
+ <button
+ onclick="testShowWarning(this, {shippingAddress: {regionCode: 'Dont show this'}});"
+ >
+ Show warning if `requestShipping` if false, and
+ `errorFields.shippingAddress.regionCode` member present.
+ </button>
+ </li>
+ <li>
+ <button
+ onclick="testShowWarning(this, {shippingAddress: {sortingCode: 'Dont show this'}});"
+ >
+ Show warning if `requestShipping` if false, and
+ `errorFields.shippingAddress.sortingCode` member present.
+ </button>
+ </li>
+ <li><button onclick="done()">Done!</button></li>
+</ol>
diff --git a/testing/web-platform/tests/payment-request/payment-response/shippingAddress-attribute-manual.https.html b/testing/web-platform/tests/payment-request/payment-response/shippingAddress-attribute-manual.https.html
new file mode 100644
index 0000000000..f9f0a6e4fa
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/payment-response/shippingAddress-attribute-manual.https.html
@@ -0,0 +1,101 @@
+<!doctype html>
+<meta charset="utf8">
+<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentresponse-shippingaddress">
+<title>
+ PaymentResponse.prototype.shippingAddress
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="helpers.js"></script>
+<script>
+async function checkNullShippingAddress(button, options) {
+ button.disabled = true;
+ const { request, response } = await getPaymentRequestResponse(options);
+ await response.complete();
+ test(() => {
+ assert_idl_attribute(response, "shippingAddress");
+ assert_equals(
+ response.shippingAddress,
+ null,
+ "Expected response.shippingAddress to be null"
+ );
+ assert_equals(
+ request.shippingAddress,
+ null,
+ "Expected request.shippingAddress to be null"
+ );
+ }, button.textContent.trim());
+}
+
+async function runManualTest(button, options = {}, expected = {}, id) {
+ button.disabled = true;
+ const { request, response } = await getPaymentRequestResponse(options, id);
+ await response.complete();
+ test(() => {
+ assert_equals(response.requestId, request.id, `Expected ids to match`);
+ assert_idl_attribute(response, "shippingAddress");
+ const { shippingAddress: addr } = request;
+ assert_true(
+ addr instanceof ContactAddress,
+ "Expect instance of ContactAddress"
+ );
+ try {
+ addr.toJSON();
+ } catch (err) {
+ assert_unreached(
+ `Unexpected exception calling ContactAddress.toJSON(): ${err}`
+ );
+ }
+ if (expected.shippingAddress === null) {
+ return;
+ }
+ assert_equals(addr.country, "AF", "Expected AF for country");
+ assert_true(
+ addr.addressLine instanceof Array,
+ "Expected addressLine to be an array"
+ );
+ assert_equals(
+ addr.addressLine.toString(),
+ "1 wpt street",
+ "Expected '1 wpt street' for addressLine"
+ );
+ assert_equals(addr.city, "Kabul", "Expected city to be Kabul");
+ assert_equals(addr.postalCode, "1001", "Expect postalCode to be 1001");
+ assert_equals(addr.recipient, "web platform test");
+ }, button.textContent.trim());
+}
+</script>
+<h2>shippingAddress attribute</h2>
+<p>
+ Click on each button in sequence from top to bottom without refreshing the page.
+ Each button will bring up the Payment Request UI window.
+</p>
+<p>
+ When prompted, please enter "web platform test" as recipient, at address "1 wpt street" in "Kabul, Afghanistan", zip/postal code 1001.
+</p>
+<ol>
+ <li>
+ <button onclick="checkNullShippingAddress(this, undefined)">
+ If options is undefined, then shippingAddress must be null.
+ </button>
+ </li>
+ <li>
+ <button onclick="checkNullShippingAddress(this, {})">
+ If the requestShipping member is missing, then shippingAddress must be null.
+ </button>
+ </li>
+ <li>
+ <button onclick="checkNullShippingAddress(this, {requestShipping: false})">
+ If the requestShipping member is false, then shippingAddress must be null.
+ </button>
+ </li>
+ <li>
+ <button onclick="runManualTest(this, {requestShipping: true}).then(done)">
+ If the requestShipping member is true, then shippingAddress must be "1 wpt street" in "Kabul, Afghanistan", zip code 1001.
+ </button>
+ </li>
+</ol>
+<small>
+ If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a>
+ and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>.
+</small>
diff --git a/testing/web-platform/tests/payment-request/payment-response/shippingOption-attribute-manual.https.html b/testing/web-platform/tests/payment-request/payment-response/shippingOption-attribute-manual.https.html
new file mode 100644
index 0000000000..687d3a52de
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/payment-response/shippingOption-attribute-manual.https.html
@@ -0,0 +1,43 @@
+<!doctype html>
+<meta charset="utf8">
+<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentresponse-shippingoption">
+<title>
+ PaymentResponse.prototype.complete() method
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="helpers.js"></script>
+<h2>shippingOption attribute</h2>
+<p>
+ Click on each button in sequence from top to bottom without refreshing the page.
+ Each button will bring up the Payment Request UI window.
+</p>
+<p>
+ For the last test, please select the only available shipping option and select "Pay".
+</p>
+<ol>
+ <li>
+ <button onclick="runManualTest(this, undefined, { shippingOption: null })">
+ If the options is undefined, then shippingOption must be null.
+ </button>
+ </li>
+ <li>
+ <button onclick="runManualTest(this, { requestShipping: undefined }, { shippingOption: null })">
+ If the requestShipping member is missing, then shippingOption must be null.
+ </button>
+ </li>
+ <li>
+ <button onclick="runManualTest(this, { requestShipping: false }, { shippingOption: null })">
+ If the requestShipping member is false, then shippingOption must be null.
+ </button>
+ </li>
+ <li>
+ <button onclick="runManualTest(this, { requestShipping: true }, { shippingOption: 'pass' }).then(done)">
+ If the requestShipping member is true, then shippingOption must be the id of the selected shipping option ("pass").
+ </button>
+ </li>
+</ol>
+<small>
+ If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a>
+ and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>.
+</small>
diff --git a/testing/web-platform/tests/payment-request/shipping-address-changed-manual.https.html b/testing/web-platform/tests/payment-request/shipping-address-changed-manual.https.html
new file mode 100644
index 0000000000..aad57cd724
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/shipping-address-changed-manual.https.html
@@ -0,0 +1,99 @@
+<!DOCTYPE html>
+<!-- Copyright Β© 2017 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
+<meta charset="utf-8">
+<title>Test for PaymentRequest shippingAddress attribute</title>
+<link rel="help" href="https://w3c.github.io/payment-request/#shippingaddress-attribute">
+<link rel="help" href="https://w3c.github.io/payment-request/#onshippingaddresschange-attribute">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({ explicit_done: true, explicit_timeout: true });
+const applePay = Object.freeze({
+ supportedMethods: "https://apple.com/apple-pay",
+ data: {
+ version: 3,
+ merchantIdentifier: "merchant.com.example",
+ countryCode: "US",
+ merchantCapabilities: ["supports3DS"],
+ supportedNetworks: ["visa"],
+ }
+});
+const validMethod = Object.freeze({ supportedMethods: "basic-card" });
+const validMethods = Object.freeze([validMethod, applePay]);
+const validAmount = Object.freeze({ currency: "USD", value: "5.00" });
+const validTotal = Object.freeze({
+ label: "label",
+ amount: validAmount,
+});
+const validShippingOption = Object.freeze({
+ id: "valid",
+ label: "Shipping Option",
+ amount: validAmount,
+ selected: false,
+});
+const validDetails = Object.freeze({
+ total: validTotal,
+ shippingOptions: [validShippingOption],
+});
+const requestShipping = Object.freeze({
+ requestShipping: true,
+});
+
+function testShippingAddressChange() {
+ promise_test(async t => {
+ const request = new PaymentRequest(
+ validMethods,
+ validDetails,
+ requestShipping
+ );
+ assert_equals(
+ request.shippingAddress,
+ null,
+ "request.shippingAddress must initially be null"
+ );
+ const listenerPromise = new Promise(resolve => {
+ request.addEventListener("shippingaddresschange", () => {
+ resolve(request.shippingAddress);
+ });
+ });
+ const handlerPromise = new Promise(resolve => {
+ request.onshippingaddresschange = () => {
+ resolve(request.shippingAddress);
+ };
+ });
+ request.show().catch(err => err);
+ const results = await Promise.all([listenerPromise, handlerPromise]);
+ results.forEach(obj => {
+ assert_true(obj instanceof ContactAddress,
+ "Expected instance of ContactAddress");
+ assert_equals(obj.organization, "", "organization should be redacted");
+ assert_equals(obj.phone, "", "phone should be redacted");
+ assert_equals(obj.recipient, "", "recipient should be redacted");
+ assert_equals(obj.addressLine.length, 0, "addressLine should be redacted");
+ });
+ await request.abort();
+ });
+ done();
+}
+
+</script>
+
+<h2>PaymentRequest shippingAddress attribute</h2>
+<p>
+ Click on each button in sequence from top to bottom without refreshing the page.
+ Each button will bring up the Payment Request UI window.
+</p>
+<p>
+ When the payment sheet is presented, enter or select a shipping address.
+</p>
+<ol>
+ <li>
+ <button onclick="testShippingAddressChange()">
+ When the shipping address is manually changed, request.shippingAddress is a ContactAddress.
+ </button>
+ </li>
+</ol>
+<small>
+ If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a>
+ and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>.
+</small>
diff --git a/testing/web-platform/tests/payment-request/show-method-optional-promise-rejects.https.html b/testing/web-platform/tests/payment-request/show-method-optional-promise-rejects.https.html
index 4a41f28fc9..3b42965503 100644
--- a/testing/web-platform/tests/payment-request/show-method-optional-promise-rejects.https.html
+++ b/testing/web-platform/tests/payment-request/show-method-optional-promise-rejects.https.html
@@ -84,6 +84,10 @@
total: invalidNegativeTotal,
});
+ // PaymentOptions
+ const validOptions = Object.freeze({
+ requestShipping: true,
+ });
// PaymentItem
const validPaymentItem = Object.freeze({
@@ -100,6 +104,24 @@
const validPaymentItems = Object.freeze([validPaymentItem]);
const invalidPaymentItems = Object.freeze([invalidPaymentItem]);
+ // PaymentShippingOption
+ const invalidShippingOption = Object.freeze({
+ id: "abc",
+ label: "Invalid shipping option",
+ amount: invalidAmount,
+ selected: true,
+ });
+
+ // PaymentShippingOptions
+ const validShippingOption = Object.freeze({
+ id: "abc",
+ label: "valid shipping option",
+ amount: validAmount,
+ });
+
+ const validShippingOptions = Object.freeze([validShippingOption]);
+ const invalidShippingOptions = Object.freeze([invalidShippingOption]);
+
// PaymentDetailsModifier
const validModifier = Object.freeze({
additionalDisplayItems: validPaymentItems,
@@ -144,7 +166,8 @@
promise_test(async (t) => {
const request = new PaymentRequest(
validMethods,
- validDetails
+ validDetails,
+ validOptions
);
await test_driver.bless("Payment request");
const detailsPromise = Promise.resolve(badDetails);
@@ -194,6 +217,21 @@
);
testBadUpdate(
+ "Updating with duplicate shippingOptions (same IDs) results in a TypeError.",
+ {
+ ...validDetails,
+ shippingOptions: [validShippingOption, validShippingOption],
+ },
+ TypeError
+ );
+
+ testBadUpdate(
+ "Updating with a shippingOption with an invalid currency value results in a RangError.",
+ { ...validDetails, shippingOptions: invalidShippingOptions },
+ RangeError
+ );
+
+ testBadUpdate(
"Must throw a RangeError when a modifier's total item has an invalid currency.",
{ ...validDetails, modifiers: [modifierWithInvalidTotal, validModifier] },
RangeError
diff --git a/testing/web-platform/tests/payment-request/show-method-optional-promise-resolves-manual.https.html b/testing/web-platform/tests/payment-request/show-method-optional-promise-resolves-manual.https.html
new file mode 100644
index 0000000000..5360a9704a
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/show-method-optional-promise-resolves-manual.https.html
@@ -0,0 +1,339 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test for PaymentRequest.show(optional promise) method</title>
+<link rel="help" href="https://w3c.github.io/browser-payment-api/#show-method">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+"use strict";
+setup({
+ allow_uncaught_exception: true,
+ explicit_done: true,
+ explicit_timeout: true,
+});
+
+// DATA USED BY TESTS
+// PaymentMethods
+const validMethods = Object.freeze([
+ {
+ supportedMethods: "valid-but-wont-ever-match",
+ },
+ {
+ supportedMethods: "basic-card",
+ },
+ {
+ supportedMethods: "https://apple.com/apple-pay",
+ data: {
+ version: 3,
+ merchantIdentifier: "merchant.com.example",
+ countryCode: "US",
+ merchantCapabilities: ["supports3DS"],
+ supportedNetworks: ["visa"],
+ }
+ },
+]);
+
+// Amounts
+const failAmount = Object.freeze({
+ currency: "USD",
+ value: "1.00",
+});
+const passAmount = Object.freeze({
+ currency: "CAD",
+ value: "50.00",
+});
+const neutralAmount = Object.freeze({
+ currency: "AUD",
+ value: "0.00",
+});
+
+// Labels
+const failLabel = "πŸ’₯ TEST HAS FAILED πŸ’₯";
+const passLabel = "βœ… TEST HAS PASSED βœ…";
+const neutralLabel = "Ignore this label";
+// Totals
+const failTotal = Object.freeze({
+ label: failLabel,
+ amount: failAmount,
+});
+const passTotal = Object.freeze({
+ label: passLabel,
+ amount: passAmount,
+});
+const neutralTotal = Object.freeze({
+ label: neutralLabel,
+ amount: passAmount,
+});
+
+// PaymentItem
+const failPaymentItem = Object.freeze({
+ amount: failAmount,
+ label: failLabel,
+});
+const failPaymentItems = Object.freeze([failPaymentItem]);
+
+const passPaymentItem = Object.freeze({
+ amount: passAmount,
+ label: passLabel,
+});
+const passPaymentItems = Object.freeze([passPaymentItem]);
+
+// PaymentShippingOptions
+const failShippingOption = Object.freeze({
+ id: "fail",
+ label: failLabel,
+ amount: failAmount,
+});
+const failShippingOptions = Object.freeze([failShippingOption]);
+
+const neutralShippingOption = Object.freeze({
+ id: "neutral",
+ label: neutralLabel,
+ amount: neutralAmount,
+});
+
+const updatedShippingOption1 = Object.freeze({
+ id: "updated-1",
+ label: `${passLabel} - option 1`,
+ amount: passAmount,
+});
+const updatedShippingOption2 = Object.freeze({
+ id: "updated-2",
+ label: `${passLabel} - option 2 (MUST BE SELECTED!)`,
+ amount: passAmount,
+ selected: true,
+});
+const passShippingOptions = Object.freeze([
+ updatedShippingOption1,
+ updatedShippingOption2,
+]);
+
+// Modifiers
+// create a modifier objects for each validMethods
+// and single additional display item
+const failModifiers = validMethods.map(modifier => {
+ const label = `${failLabel} - (${modifier.supportedMethods})`;
+ return {
+ ...modifier,
+ total: {
+ ...failTotal,
+ label,
+ },
+ additionalDisplayItems: [
+ {
+ ...failPaymentItem,
+ label,
+ },
+ ],
+ };
+});
+// Updates the total for each, and changes the additionalDisplayItems
+const passModifiers = failModifiers.map(modifier => {
+ const label = `${passLabel} - (${modifier.supportedMethods})`;
+ return {
+ ...modifier,
+ total: {
+ ...passTotal,
+ label,
+ },
+ additionalDisplayItems: [
+ {
+ ...passPaymentItem,
+ label,
+ },
+ ],
+ };
+});
+
+// PaymentDetailsInit
+const failDetails = Object.freeze({
+ displayItems: failPaymentItems,
+ id: "this cannot be changed",
+ modifiers: failModifiers,
+ shippingOptions: failShippingOptions,
+ total: failTotal,
+});
+
+const neutralDetails = Object.freeze({
+ displayItems: [],
+ modifiers: [],
+ shippingOptions: [neutralShippingOption],
+ total: neutralTotal,
+});
+
+function smokeTest() {
+ promise_test(async t => {
+ const request = new PaymentRequest(validMethods, failDetails);
+ await promise_rejects_js(
+ t,
+ TypeError,
+ request.show({
+ total: "This throws a TypeError",
+ }),
+ "expected TypeError"
+ );
+ }, "smoke test - checks if the optional details are supported on show() method");
+}
+
+function runUpdateDetailsAlgorithm(
+ buttonElement,
+ details,
+ options = {
+ requestShipping: true,
+ },
+ initialDetails = failDetails,
+) {
+ const testAssertion = buttonElement.textContent.trim();
+ buttonElement.disabled = true;
+ promise_test(async t => {
+ const request = new PaymentRequest(validMethods, initialDetails, options);
+ const detailsPromise = Promise.resolve(details);
+ const acceptPromise = request.show(detailsPromise);
+ assert_equals(request.id, "this cannot be changed", "id must never change.");
+ await promise_rejects_dom(
+ t,
+ "AbortError",
+ acceptPromise,
+ "expected AbortError"
+ );
+ }, testAssertion);
+}
+</script>
+<h2>
+ PaymentRequest <code>.show(optional detailsPromise)</code> tests
+</h2>
+<p>
+ These test cause <code>detailsPromise</code> to resolve successfully with some updated value. As such, that will cause
+ something in the payment sheet to change. Each test describes what is expected to change - if anything.
+</p>
+<p>
+ <strong>Instructions:</strong> Click on each button in sequence from top to bottom without refreshing the page. The payment
+ sheet will be shown. If required, confirm that the expected value appears in the payment sheet. Finally, manually abort/cancel
+ the payment request by closing the payment sheet.
+</p>
+<ol>
+ <li><button onclick="smokeTest()">If the payment sheet is shown, the test has failed.</button></li>
+ <li><button onclick="
+ const details = {
+ ...neutralDetails,
+ id: 'fail',
+ };
+ runUpdateDetailsAlgorithm(this, details);
+ ">
+ When the payment sheet is shown, the provided `id` must have no effect on the payment request.
+ </button></li>
+ <li><button onclick="
+ const details = {
+ ...neutralDetails,
+ total: passTotal
+ };
+ runUpdateDetailsAlgorithm(this, details);
+ ">
+ When the payment sheet is shown, the total must be CAD$50 with the label "βœ… TEST HAS PASSED βœ…".
+ </button></li>
+ <li><button onclick="
+ const details = {
+ ...neutralDetails,
+ displayItems: passPaymentItems,
+ };
+ runUpdateDetailsAlgorithm(this, details);
+ ">
+ When the payment sheet is shown, there must be a one display item with a value of CAD$50 with the label "βœ… TEST HAS PASSED βœ…".
+ </button>
+ </li>
+ <li><button onclick="
+ const auItem = {
+ ...passPaymentItem,
+ amount: { value: '40', currency: 'AUD'},
+ pending: true
+ }
+ const details = {
+ ...neutralDetails,
+ displayItems: passPaymentItems.concat(auItem),
+ };
+ runUpdateDetailsAlgorithm(this, details);
+ ">
+ When the payment sheet is shown, there must be
+ two display items: One with a value of CAD$50, another with
+ value AUD$40 that is pending.
+ </button>
+ </li>
+ <li><button onclick="
+ const details = {
+ ...neutralDetails,
+ shippingOptions: [updatedShippingOption1],
+ };
+ runUpdateDetailsAlgorithm(this, details);
+ ">
+ When the payment sheet is shown, there must be a one shipping option
+ with a value of CAD$50.
+ </button>
+ </li>
+ <li><button onclick="
+ const details = {
+ ...neutralDetails,
+ shippingOptions: passShippingOptions,
+ };
+ runUpdateDetailsAlgorithm(this, details);
+ ">
+ When the payment sheet is shown, there must be
+ two shipping options: One with a value of CAD$50, another with
+ value AUD$40 that is selected.
+ </button>
+ </li>
+ <li><button onclick="
+ const details = {
+ ...neutralDetails,
+ modifiers: passModifiers,
+ };
+ runUpdateDetailsAlgorithm(this, details);
+ ">
+ When the payment sheet is shown, the total should be CAD$50.
+ </button>
+ </li>
+ <li>
+ <button onclick="
+ const details = {
+ ...neutralDetails,
+ shippingOptions: [],
+ error: passLabel,
+ };
+ runUpdateDetailsAlgorithm(this, details);
+ ">
+ When the payment sheet is shown, the string "βœ… TEST HAS PASSED βœ…" should be shown
+ somewhere in the user interface. Alternatively, the payment sheet must indicate to the
+ end user that it's not possible to ship their order.
+ </button>
+ </li>
+ <li>
+ <button onclick="
+ const details = {
+ ...neutralDetails,
+ error: failLabel,
+ };
+ runUpdateDetailsAlgorithm(this, details, {requestShipping: false});
+ ">
+ When the payment sheet is shown, there should not be any errors shown.
+ </button>
+ </li>
+ <li>
+ <button onclick="
+ const initialDetails = {total: passTotal, id: 'this cannot be changed'};
+ const updatedDetails = {};
+ runUpdateDetailsAlgorithm(
+ this, updatedDetails, {requestShipping: false}, initialDetails);
+ ">
+ Resolving the show promise with empty details will preserve the details from
+ the constructor. When the payment sheet is shown, the string
+ "βœ… TEST HAS PASSED βœ…" should be shown.
+ </button>
+ </li>
+ <li>
+ <button onclick="done();">Done!</button>
+ </li>
+</ol>
+
+<small>
+ If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a>
+ and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>.
+</small>
diff --git a/testing/web-platform/tests/payment-request/updateWith-method-pmi-handling-manual.https.html b/testing/web-platform/tests/payment-request/updateWith-method-pmi-handling-manual.https.html
new file mode 100644
index 0000000000..1a52978ca3
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/updateWith-method-pmi-handling-manual.https.html
@@ -0,0 +1,140 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test for validity of payment method identifiers when calling updateWith() method</title>
+<link rel="help" href="https://www.w3.org/TR/payment-request/#updatewith()-method">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+"use strict";
+setup({
+ explicit_done: true,
+ explicit_timeout: true,
+ allow_uncaught_exception: true,
+});
+const applePay = Object.freeze({
+ supportedMethods: "https://apple.com/apple-pay",
+ data: {
+ version: 3,
+ merchantIdentifier: "merchant.com.example",
+ countryCode: "US",
+ merchantCapabilities: ["supports3DS"],
+ supportedNetworks: ["visa"],
+ },
+});
+const validMethod = Object.freeze({
+ supportedMethods: "https://:@wpt.fyi:443/payment-request",
+});
+
+const validMethods = Object.freeze([
+ validMethod,
+ applePay,
+ { supportedMethods: "basic-card" },
+]);
+
+const validAmount = Object.freeze({
+ currency: "USD",
+ value: "1.0",
+});
+
+const validTotal = Object.freeze({
+ label: "Default Total",
+ amount: validAmount,
+});
+
+const validShippingOption = Object.freeze({
+ id: "standard",
+ label: "Shipping option",
+ amount: validAmount,
+ selected: true,
+});
+
+const validDetails = Object.freeze({
+ total: validTotal,
+ shippingOptions: [validShippingOption],
+});
+
+const validModifier = Object.freeze({
+ supportedMethods: "basic-card",
+ total: validTotal,
+});
+
+function manualTest(button, { invalidMethod }) {
+ button.disabled = true;
+ promise_test(async t => {
+ const request = new PaymentRequest(validMethods, validDetails, {
+ requestShipping: true,
+ });
+ const listener = ev => {
+ const invalidModifier = Object.assign({}, validModifier, {
+ supportedMethods: invalidMethod,
+ });
+ const invalidDetails = Object.assign({}, validDetails, {
+ modifiers: [validModifier, invalidModifier],
+ });
+ ev.updateWith(invalidDetails);
+ };
+ // We test against a valid and an invalid modifier
+ request.addEventListener("shippingaddresschange", listener, { once: true });
+ const showPromise = request.show();
+ await promise_rejects_js(t, RangeError, showPromise);
+ }, button.textContent.trim());
+}
+</script>
+<h2>updateWith() method: test validity of payment method identifiers.</h2>
+<p>
+ When shown a payment sheet, select a different address.
+</p>
+<ol>
+ <li>
+ <button onclick="manualTest(this, {invalidMethod: 'https://:password@example.com'});">
+ Must throw if the URL has a password.
+ </button>
+ </li>
+ <li>
+ <button onclick="manualTest(this, {invalidMethod: 'https://username@example.com'});">
+ Must throw if the URL has a username.
+ </button>
+ </li>
+ <li>
+ <button onclick="manualTest(this, {invalidMethod: 'https://username:password@example.com/pay'});">
+ Must throw if the URL has a username and a password.
+ </button>
+ </li>
+ <li>
+ <button onclick="manualTest(this, {invalidMethod: 'http://username:password@example.com/pay'});">
+ Must throw if it's http, and has a username and password.
+ </button>
+ </li>
+ <li>
+ <button onclick="manualTest(this, {invalidMethod: 'http://foo.com:100000000/pay'});">
+ Must throw if the URL is invalid (port range).
+ </button>
+ </li>
+ <li>
+ <button onclick="manualTest(this, {invalidMethod: 'basic-πŸ’³'});">
+ Must throw if the PMI contains characters that are out of range.
+ </button>
+ </li>
+ <li>
+ <button onclick="manualTest(this, {invalidMethod: 'not-https://wpt.fyi/payment-request'});">
+ Must throw if not https.
+ </button>
+ </li>
+ <li>
+ <button onclick="manualTest(this, {invalidMethod: 'Β‘basic-*-card!'});">
+ Must throw if the standardized PMI contains characters outside the ascii range.
+ </button>
+ </li>
+ <li>
+ <button onclick="manualTest(this, {invalidMethod: 'Basic-Card'});">
+ Must throw if standardized PMI has uppercase characters.
+ </button>
+ </li>
+ <li>
+ <button onclick="done();">Done!</button>
+ </li>
+</ol>
+<small>
+ If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a>
+ and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>.
+</small>
diff --git a/testing/web-platform/tests/payment-request/user-accepts-payment-request-algo-manual.https.html b/testing/web-platform/tests/payment-request/user-accepts-payment-request-algo-manual.https.html
new file mode 100644
index 0000000000..300f04811f
--- /dev/null
+++ b/testing/web-platform/tests/payment-request/user-accepts-payment-request-algo-manual.https.html
@@ -0,0 +1,230 @@
+<!doctype html>
+<meta charset="utf8">
+<link rel="help" href="https://w3c.github.io/payment-request/#user-accepts-the-payment-request-algorithm">
+<title>
+ User accepts the payment request algorithm
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({ explicit_done: true, explicit_timeout: true });
+const applePay = Object.freeze({
+ supportedMethods: "https://apple.com/apple-pay",
+ data: {
+ version: 3,
+ merchantIdentifier: "merchant.com.example",
+ countryCode: "US",
+ merchantCapabilities: ["supports3DS"],
+ supportedNetworks: ["visa"],
+ }
+});
+const basicCardMethod = Object.freeze({
+ supportedMethods: "basic-card",
+});
+const validMethod = Object.freeze({
+ supportedMethods: "this-is-just-for-testings-will-never-match",
+});
+const methods = Object.freeze([basicCardMethod, validMethod, applePay]);
+const validAmount = Object.freeze({
+ currency: "USD",
+ value: "5.00",
+});
+const shippingOptions = [
+ Object.freeze({
+ id: "option1",
+ label: "Option 1",
+ amount: validAmount,
+ selected: false,
+ }),
+ Object.freeze({
+ id: "option 2",
+ label: "Option 2",
+ amount: validAmount,
+ selected: true,
+ }),
+];
+
+const detailsNoShippingOptions = Object.freeze({
+ total: {
+ label: "Total due",
+ amount: validAmount,
+ },
+});
+
+const detailsWithShippingOptions = Object.assign({}, detailsNoShippingOptions, {
+ shippingOptions,
+});
+
+const optionsRequestNothing = Object.freeze({
+ requestShipping: false,
+ requestPayerEmail: false,
+ requestPayerName: false,
+ requestPayerPhone: false,
+});
+
+const optionsRequestEverything = Object.freeze({
+ requestShipping: true,
+ requestPayerEmail: true,
+ requestPayerName: true,
+ requestPayerPhone: true,
+});
+
+test(() => {
+ // smoke test
+ try {
+ new PaymentRequest(methods, detailsNoShippingOptions);
+ } catch (err) {
+ done();
+ throw err;
+ }
+}, "Must be able to construct a payment request (smoke test)");
+
+function testAcceptRequestAlgorithm(
+ button,
+ details,
+ options = {},
+ expectedResponse = {}
+) {
+ button.disabled = true;
+ promise_test(async t => {
+ const request = new PaymentRequest(methods, details, options);
+ const response = await request.show();
+ assert_true(
+ response instanceof PaymentResponse,
+ "Expected an instance of PaymentResponse."
+ );
+ // Response [[calledComplete]] is false, so this shouldn't throw
+ await response.complete("success");
+ // For good measure, test that subsequent complete()
+ for (const state of [undefined, "success", "fail", "unknown"]) {
+ await promise_rejects_dom(
+ t,
+ "InvalidStateError",
+ response.complete(state),
+ "Response [[calledComplete]] is true, so InvalidStateError"
+ );
+ }
+ assert_equals(
+ request.id,
+ response.requestId,
+ "Request and response ids must match."
+ );
+ assert_true(response.details instanceof Object, "Expected an object");
+ assert_equals(
+ response.shippingAddress,
+ request.shippingAddress,
+ "Request and response must reference same shippingAddress (or both null)."
+ );
+ assert_equals(
+ response.shippingOption,
+ request.shippingOption,
+ "Request and response must be the same value (or both null)."
+ );
+ if (options.requestShipping === true) {
+ assert_true(
+ response.shippingAddress instanceof ContactAddress,
+ "Expected an instance of ContactAddress."
+ );
+ }
+ const expected = {
+ methodName: "basic-card",
+ payerEmail: options.requestPayerEmail
+ ? expectedResponse.payerEmail
+ : null,
+ payerName: options.requestPayerName ? expectedResponse.payerName : null,
+ payerPhone: options.requestPayerPhone
+ ? expectedResponse.payerPhone
+ : null,
+ };
+ for (const [attr, expectedValue] of Object.entries(expected)) {
+ assert_equals(
+ response[attr],
+ expectedValue,
+ `response.${attr} must be ${expectedValue}`
+ );
+ }
+ await promise_rejects_dom(
+ t,
+ "InvalidStateError",
+ request.show(),
+ "Request [[state]] is closed, so InvalidStateError"
+ );
+ }, button.textContent.trim());
+}
+
+</script>
+
+<section>
+ <h2 id="user-accepts-payment-request">User accepts payment request</h2>
+ <p>
+ Click on each button in sequence from top to bottom without refreshing the page.
+ Each button will bring up the Payment Request UI window.
+ </p>
+ <p>
+ When shown the payment sheet, please input a credit card and select Pay.
+ </p>
+ <ol>
+ <li>
+ <button onclick="
+ const detailsWithId = Object.assign({}, detailsNoShippingOptions, { id: 'pass' });
+ testAcceptRequestAlgorithm(this, detailsWithId, optionsRequestNothing);">
+ User accepts payment request, but not shipping is requested.
+ </button> Use any credit card to pay.
+ </li>
+ <li>
+ <button onclick="
+ const requestShipping = Object.assign({}, optionsRequestNothing, {requestShipping: true});
+ const expectedValues = { shippingOption: 'option 2' };
+ testAcceptRequestAlgorithm(this, detailsWithShippingOptions, requestShipping, expectedValues);">
+ User accepts payment request, merchant requests shipping.
+ </button> Select any shipping option, and use any credit card to pay.
+ </li>
+ <li>
+ <button onclick="
+ const requestPayerEmail = Object.assign({}, optionsRequestNothing, {requestPayerEmail: true});
+ const expectValues = { payerEmail: 'wpt@w3.org' };
+ testAcceptRequestAlgorithm(this, detailsNoShippingOptions, requestPayerEmail, expectValues);">
+ User accepts payment request, merchant requests email.
+ </button>
+ When prompted, please use "wpt@w3.org" as the email.
+ </li>
+ <li>
+ <button onclick="
+ const requestPayerPhone = Object.assign({}, optionsRequestNothing, {requestPayerPhone: true});
+ const expectValues = { payerPhone: '+12345678910' };
+ testAcceptRequestAlgorithm(this, detailsNoShippingOptions, requestPayerPhone, expectValues);">
+ User accepts payment request, merchant requests phone.
+ </button>
+ When prompted, please use "+12345678910" as the phone number.
+ </li>
+ <li>
+ <button onclick="
+ const requestPayerName = Object.assign({}, optionsRequestNothing, {requestPayerName: true});
+ const expectValues = { payerName: 'web platform test' };
+ testAcceptRequestAlgorithm(this, detailsNoShippingOptions, requestPayerName, expectValues);">
+ User accepts payment request, merchant requests payer's name.
+ </button>
+ When prompted, please use "web platform test" as the payer name.
+ </li>
+ <li>
+ <button onclick="
+ const expectValues = {
+ payerEmail: 'wpt@w3.org',
+ payerName: 'web platform test',
+ payerPhone: '+12345678910',
+ shippingOption: 'option 2',
+ };
+ testAcceptRequestAlgorithm(this, detailsWithShippingOptions, optionsRequestEverything, expectValues);">
+ User accepts payment request, merchant requests everything.
+ </button>
+ When prompted, please use: "+12345678910" as the phone number, "web platform test" as the payer name, and "wpt@w3.org" as the email. Then press Pay.
+ </li>
+ <li>
+ <button onclick="done()">Done</button>
+ </li>
+ </ol>
+</section>
+<small>
+ If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a>
+ and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>.
+</small>