Bug 1440499 - Implement the payerName/payerEmail/payerPhone contact picker. r=MattN
☠☠ backed out by 5fb1b1d9d669 ☠ ☠
authorSam Foster <sfoster@mozilla.com>
Tue, 06 Mar 2018 14:00:05 -0800
changeset 407276 4e2e081dad557a8c060a3751bb1986c9686d5fe2
parent 407275 479b143d88284a89d9d9bde4e69798de2a398fbd
child 407277 e1641b8d914d5cadcf9716b8dcaa6a08745fed6c
push id60872
push usersfoster@mozilla.com
push dateFri, 09 Mar 2018 00:26:12 +0000
treeherderautoland@4e2e081dad55 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMattN
bugs1440499
milestone60.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1440499 - Implement the payerName/payerEmail/payerPhone contact picker. r=MattN * Based on original patch by MattN * Make stored contacts available as payer data (MattN) * Add the address-picker element to the dialog for selecting payer details from stored contacts (MattN) * Add a field-names attribute to the payer address-picker, populated from the request paymentOptions * Basic CSS to selectively render address fields * Add mochitests to verify paymentOptions result in the correct payment picker behavior MozReview-Commit-ID: Br8i5MVyeQ3
toolkit/components/payments/content/paymentDialogWrapper.js
toolkit/components/payments/res/components/address-option.css
toolkit/components/payments/res/containers/address-picker.js
toolkit/components/payments/res/containers/payment-dialog.js
toolkit/components/payments/res/debugging.js
toolkit/components/payments/res/mixins/PaymentStateSubscriberMixin.js
toolkit/components/payments/res/paymentRequest.xhtml
toolkit/components/payments/test/mochitest/mochitest.ini
toolkit/components/payments/test/mochitest/payments_common.js
toolkit/components/payments/test/mochitest/test_order_details.html
toolkit/components/payments/test/mochitest/test_payer_address_picker.html
--- a/toolkit/components/payments/content/paymentDialogWrapper.js
+++ b/toolkit/components/payments/content/paymentDialogWrapper.js
@@ -42,16 +42,43 @@ var paymentDialogWrapper = {
     Ci.nsIObserver,
     Ci.nsISupportsWeakReference,
   ]),
 
   /**
    * Note: This method is async because formAutofillStorage plans to become async.
    *
    * @param {string} guid
+   * @returns {object} containing only the requested payer values.
+   */
+  async _convertProfileAddressToPayerData(guid) {
+    let addressData = profileStorage.addresses.get(guid);
+    if (!addressData) {
+      throw new Error(`Payer address not found: ${guid}`);
+    }
+
+    let {
+      requestPayerName,
+      requestPayerEmail,
+      requestPayerPhone,
+    } = this.request.paymentOptions;
+
+    let payerData = {
+      payerName: requestPayerName ? addressData.name : "",
+      payerEmail: requestPayerEmail ? addressData.email : "",
+      payerPhone: requestPayerPhone ? addressData.tel : "",
+    };
+
+    return payerData;
+  },
+
+  /**
+   * Note: This method is async because profileStorage plans to become async.
+   *
+   * @param {string} guid
    * @returns {nsIPaymentAddress}
    */
   async _convertProfileAddressToPaymentAddress(guid) {
     let addressData = formAutofillStorage.addresses.get(guid);
     if (!addressData) {
       throw new Error(`Shipping address not found: ${guid}`);
     }
 
@@ -370,32 +397,42 @@ var paymentDialogWrapper = {
     const showResponse = this.createShowResponse({
       acceptStatus: Ci.nsIPaymentActionResponse.PAYMENT_REJECTED,
     });
     paymentSrv.respondPayment(showResponse);
     window.close();
   },
 
   async onPay({
+    selectedPayerAddressGUID: payerGUID,
     selectedPaymentCardGUID: paymentCardGUID,
     selectedPaymentCardSecurityCode: cardSecurityCode,
   }) {
     let methodData = await this._convertProfileBasicCardToPaymentMethodData(paymentCardGUID,
                                                                             cardSecurityCode);
 
     if (!methodData) {
       // TODO (Bug 1429265/Bug 1429205): Handle when a user hits cancel on the
       // Master Password dialog.
       Cu.reportError("Bug 1429265/Bug 1429205: User canceled master password entry");
       return;
     }
 
+    let {
+      payerName,
+      payerEmail,
+      payerPhone,
+    } = await this._convertProfileAddressToPayerData(payerGUID);
+
     this.pay({
       methodName: "basic-card",
       methodData,
+      payerName,
+      payerEmail,
+      payerPhone,
     });
   },
 
   pay({
     payerName,
     payerEmail,
     payerPhone,
     methodName,
--- a/toolkit/components/payments/res/components/address-option.css
+++ b/toolkit/components/payments/res/components/address-option.css
@@ -12,16 +12,22 @@ address-option {
 
 rich-select[open] > .rich-select-popup-box > address-option {
   grid-template-areas:
     "name           name          "
     "street-address street-address"
     "email          tel           ";
 }
 
+address-picker.payer-related > rich-select address-option {
+  grid-template-areas:
+    "name name"
+    "tel email";
+}
+
 address-option > .name {
   grid-area: name;
 }
 
 address-option > .street-address {
   grid-area: street-address;
 }
 
@@ -35,12 +41,28 @@ address-option > .tel {
 
 address-option > .name,
 address-option > .street-address,
 address-option > .email,
 address-option > .tel {
   white-space: nowrap;
 }
 
-.rich-select-selected-clone > .email,
-.rich-select-selected-clone > .tel {
+address-picker.shipping-related address-option > .email,
+address-picker.shipping-related address-option.rich-select-selected-clone > .tel {
   display: none;
 }
+
+/* for payer contact details:
+ * display fields selectively based on the contents of the address-fields attribute
+ */
+address-picker.payer-related address-option > .name,
+address-picker.payer-related address-option > .street-address,
+address-picker.payer-related address-option > .email,
+address-picker.payer-related address-option > .tel {
+  display: none;
+}
+
+address-picker[address-fields~='name'].payer-related address-option  > .name,
+address-picker[address-fields~='email'].payer-related address-option  > .email,
+address-picker[address-fields~='tel'].payer-related address-option  > .tel {
+  display: inline-block;
+}
--- a/toolkit/components/payments/res/containers/address-picker.js
+++ b/toolkit/components/payments/res/containers/address-picker.js
@@ -28,21 +28,23 @@ class AddressPicker extends PaymentState
     let {savedAddresses} = state;
     let desiredOptions = [];
     for (let [guid, address] of Object.entries(savedAddresses)) {
       let optionEl = this.dropdown.getOptionByValue(guid);
       if (!optionEl) {
         optionEl = document.createElement("address-option");
         optionEl.value = guid;
       }
+
       for (let [key, val] of Object.entries(address)) {
         optionEl.setAttribute(key, val);
       }
       desiredOptions.push(optionEl);
     }
+
     let el = null;
     while ((el = this.dropdown.popupBox.querySelector(":scope > address-option"))) {
       el.remove();
     }
     for (let option of desiredOptions) {
       this.dropdown.popupBox.appendChild(option);
     }
 
--- a/toolkit/components/payments/res/containers/payment-dialog.js
+++ b/toolkit/components/payments/res/containers/payment-dialog.js
@@ -28,16 +28,19 @@ class PaymentDialog extends PaymentState
     this._payButton.addEventListener("click", this);
 
     this._viewAllButton = contents.querySelector("#view-all");
     this._viewAllButton.addEventListener("click", this);
 
     this._orderDetailsOverlay = contents.querySelector("#order-details-overlay");
     this._shippingTypeLabel = contents.querySelector("#shipping-type-label");
     this._shippingRelatedEls = contents.querySelectorAll(".shipping-related");
+    this._payerRelatedEls = contents.querySelectorAll(".payer-related");
+    this._payerAddressPicker = contents.querySelector("address-picker.payer-related");
+
     this._errorText = contents.querySelector("#error-text");
 
     this._disabledOverlay = contents.getElementById("disabled-overlay");
 
     this.appendChild(contents);
 
     super.connectedCallback();
   }
@@ -64,21 +67,23 @@ class PaymentDialog extends PaymentState
   }
 
   cancelRequest() {
     paymentRequest.cancel();
   }
 
   pay() {
     let {
+      selectedPayerAddress,
       selectedPaymentCard,
       selectedPaymentCardSecurityCode,
     } = this.requestStore.getState();
 
     paymentRequest.pay({
+      selectedPayerAddressGUID: selectedPayerAddress,
       selectedPaymentCardGUID: selectedPaymentCard,
       selectedPaymentCardSecurityCode,
     });
   }
 
   changeShippingAddress(shippingAddressGUID) {
     paymentRequest.changeShippingAddress({
       shippingAddressGUID,
@@ -101,16 +106,17 @@ class PaymentDialog extends PaymentState
   setStateFromParent(state) {
     this.requestStore.setState(state);
 
     // Check if any foreign-key constraints were invalidated.
     state = this.requestStore.getState();
     let {
       savedAddresses,
       savedBasicCards,
+      selectedPayerAddress,
       selectedPaymentCard,
       selectedShippingAddress,
       selectedShippingOption,
     } = state;
     let shippingOptions = state.request.paymentDetails.shippingOptions;
 
     // Ensure `selectedShippingAddress` never refers to a deleted address and refers
     // to an address if one exists.
@@ -143,16 +149,25 @@ class PaymentDialog extends PaymentState
       if (!selectedShippingOption && shippingOptions.length) {
         selectedShippingOption = shippingOptions[0].id;
       }
       this._cachedState.selectedShippingOption = selectedShippingOption;
       this.requestStore.setState({
         selectedShippingOption,
       });
     }
+
+
+    // Ensure `selectedPayerAddress` never refers to a deleted address and refers
+    // to an address if one exists.
+    if (!savedAddresses[selectedPayerAddress]) {
+      this.requestStore.setState({
+        selectedPayerAddress: Object.keys(savedAddresses)[0] || null,
+      });
+    }
   }
 
   _renderPayButton(state) {
     this._payButton.disabled = state.changesPrevented;
     switch (state.completionState) {
       case "initial":
       case "processing":
       case "success":
@@ -191,20 +206,44 @@ class PaymentDialog extends PaymentState
 
     let totalItem = paymentDetails.totalItem;
     let totalAmountEl = this.querySelector("#total > currency-amount");
     totalAmountEl.value = totalItem.amount.value;
     totalAmountEl.currency = totalItem.amount.currency;
 
     this._orderDetailsOverlay.hidden = !state.orderDetailsShowing;
     this._errorText.textContent = paymentDetails.error;
+
     let paymentOptions = request.paymentOptions;
     for (let element of this._shippingRelatedEls) {
       element.hidden = !paymentOptions.requestShipping;
     }
+    let payerRequested = paymentOptions.requestPayerName ||
+                         paymentOptions.requestPayerEmail ||
+                         paymentOptions.requestPayerPhone;
+    for (let element of this._payerRelatedEls) {
+      element.hidden = !payerRequested;
+    }
+
+    if (payerRequested) {
+      let fieldNames = new Set(); // default: ["name", "tel", "email"]
+      if (paymentOptions.requestPayerName) {
+        fieldNames.add("name");
+      }
+      if (paymentOptions.requestPayerEmail) {
+        fieldNames.add("email");
+      }
+      if (paymentOptions.requestPayerPhone) {
+        fieldNames.add("tel");
+      }
+      this._payerAddressPicker.setAttribute("address-fields", [...fieldNames].join(" "));
+    } else {
+      this._payerAddressPicker.removeAttribute("address-fields");
+    }
+
     let shippingType = paymentOptions.shippingType || "shipping";
     this._shippingTypeLabel.querySelector("label").textContent =
       this._shippingTypeLabel.dataset[shippingType + "AddressLabel"];
 
     this._renderPayButton(state);
 
     let {
       changesPrevented,
--- a/toolkit/components/payments/res/debugging.js
+++ b/toolkit/components/payments/res/debugging.js
@@ -138,16 +138,17 @@ let REQUEST_2 = {
   },
 };
 
 let ADDRESSES_1 = {
   "48bnds6854t": {
     "address-level1": "MI",
     "address-level2": "Some City",
     "country": "US",
+    "email": "foo@bar.com",
     "guid": "48bnds6854t",
     "name": "Mr. Foo",
     "postal-code": "90210",
     "street-address": "123 Sesame Street,\nApt 40",
     "tel": "+1 519 555-5555",
   },
   "68gjdh354j": {
     "address-level1": "CA",
--- a/toolkit/components/payments/res/mixins/PaymentStateSubscriberMixin.js
+++ b/toolkit/components/payments/res/mixins/PaymentStateSubscriberMixin.js
@@ -33,16 +33,17 @@ let requestStore = new PaymentsStore({
     paymentOptions: {
       requestPayerName: false,
       requestPayerEmail: false,
       requestPayerPhone: false,
       requestShipping: false,
       shippingType: "shipping",
     },
   },
+  selectedPayerAddress: null,
   selectedPaymentCard: null,
   selectedPaymentCardSecurityCode: null,
   selectedShippingAddress: null,
   selectedShippingOption: null,
   savedAddresses: {},
   savedBasicCards: {},
 });
 
--- a/toolkit/components/payments/res/paymentRequest.xhtml
+++ b/toolkit/components/payments/res/paymentRequest.xhtml
@@ -5,16 +5,17 @@
 <!DOCTYPE html [
   <!ENTITY viewAllItems               "View All Items">
   <!ENTITY paymentSummaryTitle        "Your Payment">
   <!ENTITY shippingAddressLabel       "Shipping Address">
   <!ENTITY deliveryAddressLabel       "Delivery Address">
   <!ENTITY pickupAddressLabel         "Pickup Address">
   <!ENTITY shippingOptionsLabel       "Shipping Options">
   <!ENTITY paymentMethodsLabel        "Payment Method">
+  <!ENTITY payerLabel                 "Contact Information">
   <!ENTITY cancelPaymentButton.label   "Cancel">
   <!ENTITY approvePaymentButton.label  "Pay">
   <!ENTITY processingPaymentButton.label "Processing">
   <!ENTITY successPaymentButton.label    "Done">
   <!ENTITY failPaymentButton.label       "Fail">
   <!ENTITY orderDetailsLabel          "Order Details">
   <!ENTITY orderTotalLabel            "Total">
 ]>
@@ -78,16 +79,21 @@
                data-pickup-address-label="&pickupAddressLabel;"><label></label></div>
           <address-picker class="shipping-related" selected-state-key="selectedShippingAddress"></address-picker>
 
           <div class="shipping-related"><label>&shippingOptionsLabel;</label></div>
           <shipping-option-picker class="shipping-related"></shipping-option-picker>
 
           <div><label>&paymentMethodsLabel;</label></div>
           <payment-method-picker selected-state-key="selectedPaymentCard"></payment-method-picker>
+
+          <div class="payer-related"><label>&payerLabel;</label></div>
+          <address-picker class="payer-related"
+                          selected-state-key="selectedPayerAddress"></address-picker>
+          <div id="error-text"></div>
         </section>
 
         <footer id="controls-container">
           <button id="cancel">&cancelPaymentButton.label;</button>
           <button id="pay"
                   data-initial-label="&approvePaymentButton.label;"
                   data-processing-label="&processingPaymentButton.label;"
                   data-fail-label="&failPaymentButton.label;"
--- a/toolkit/components/payments/test/mochitest/mochitest.ini
+++ b/toolkit/components/payments/test/mochitest/mochitest.ini
@@ -26,15 +26,16 @@ support-files =
    ../../res/mixins/PaymentStateSubscriberMixin.js
    ../../res/vendor/custom-elements.min.js
    ../../res/vendor/custom-elements.min.js.map
    payments_common.js
 
 [test_address_picker.html]
 [test_currency_amount.html]
 [test_order_details.html]
+[test_payer_address_picker.html]
 [test_payment_dialog.html]
 [test_payment_details_item.html]
 [test_payment_method_picker.html]
 [test_rich_select.html]
 [test_shipping_option_picker.html]
 [test_ObservedPropertiesMixin.html]
 [test_PaymentStateSubscriberMixin.html]
--- a/toolkit/components/payments/test/mochitest/payments_common.js
+++ b/toolkit/components/payments/test/mochitest/payments_common.js
@@ -1,11 +1,11 @@
 "use strict";
 
-/* exported asyncElementRendered, promiseStateChange */
+/* exported asyncElementRendered, promiseStateChange, deepClone */
 
 /**
  * A helper to await on while waiting for an asynchronous rendering of a Custom
  * Element.
  * @returns {Promise}
  */
 function asyncElementRendered() {
   return Promise.resolve();
@@ -16,8 +16,12 @@ function promiseStateChange(store) {
     store.subscribe({
       stateChangeCallback(state) {
         store.unsubscribe(this);
         resolve(state);
       },
     });
   });
 }
+
+function deepClone(obj) {
+  return JSON.parse(JSON.stringify(obj));
+}
--- a/toolkit/components/payments/test/mochitest/test_order_details.html
+++ b/toolkit/components/payments/test/mochitest/test_order_details.html
@@ -44,20 +44,16 @@
 let orderDetails = document.querySelector("order-details");
 let emptyState = requestStore.getState();
 
 function setup() {
   let initialState = deepClone(emptyState);
   requestStore.setState(initialState);
 }
 
-function deepClone(obj) {
-  return JSON.parse(JSON.stringify(obj));
-}
-
 add_task(async function isFooterItem() {
   ok(OrderDetails.isFooterItem({
     label: "Levy",
     type: "tax",
     amount: { currency: "USD", value: "1" },
   }, "items with type of 'tax' are footer items"));
   ok(!OrderDetails.isFooterItem({
     label: "Levis",
new file mode 100644
--- /dev/null
+++ b/toolkit/components/payments/test/mochitest/test_payer_address_picker.html
@@ -0,0 +1,207 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test the paymentOptions address-picker
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test the paymentOptions address-picker</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script src="payments_common.js"></script>
+
+  <script src="custom-elements.min.js"></script>
+  <script src="PaymentsStore.js"></script>
+  <script src="ObservedPropertiesMixin.js"></script>
+  <script src="PaymentStateSubscriberMixin.js"></script>
+  <script src="payment-dialog.js"></script>
+
+  <script src="rich-select.js"></script>
+  <script src="address-picker.js"></script>
+  <script src="rich-option.js"></script>
+  <script src="address-option.js"></script>
+  <script src="currency-amount.js"></script>
+  <link rel="stylesheet" type="text/css" href="rich-select.css"/>
+  <link rel="stylesheet" type="text/css" href="address-option.css"/>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <link rel="stylesheet" type="text/css" href="paymentRequest.css"/>
+</head>
+<body>
+  <p id="display">
+    <iframe id="templateFrame" src="paymentRequest.xhtml" width="0" height="0"></iframe>
+  </p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+<script type="application/javascript">
+/** Test the payer requested details functionality **/
+
+/* import-globals-from payments_common.js */
+
+function getVisiblePickerOptions(picker) {
+  let select = picker.querySelector(":scope > rich-select");
+  let options = select.querySelectorAll("address-option");
+  let visibleOptions = Array.from(options).filter(isVisible);
+  return visibleOptions;
+}
+
+function isVisible(elem) {
+  let result = elem.getBoundingClientRect().height > 0;
+  return result;
+}
+
+function setPaymentOptions(requestStore, options) {
+  let {request} = requestStore.getState();
+  request = Object.assign({}, request, {
+    paymentOptions: options,
+  });
+  return requestStore.setState({ request });
+}
+
+const SAVED_ADDRESSES = {
+  "48bnds6854t": {
+    "address-level1": "MI",
+    "address-level2": "Some City",
+    "country": "US",
+    "guid": "48bnds6854t",
+    "name": "Mr. Foo",
+    "postal-code": "90210",
+    "street-address": "123 Sesame Street,\nApt 40",
+    "tel": "+1 519 555-5555",
+    "email": "foo@example.com",
+  },
+  "68gjdh354j": {
+    "address-level1": "CA",
+    "address-level2": "Mountain View",
+    "country": "US",
+    "guid": "68gjdh354j",
+    "name": "Mrs. Bar",
+    "postal-code": "94041",
+    "street-address": "P.O. Box 123",
+    "tel": "+1 650 555-5555",
+    "email": "bar@example.com",
+  },
+};
+
+let elPicker;
+let elDialog;
+let initialState;
+
+add_task(async function setup_once() {
+  let templateFrame = document.getElementById("templateFrame");
+  await SimpleTest.promiseFocus(templateFrame.contentWindow);
+
+  let displayEl = document.getElementById("display");
+  // Import the templates from the real shipping dialog to avoid duplication.
+  for (let template of templateFrame.contentDocument.querySelectorAll("template")) {
+    let imported = document.importNode(template, true);
+    displayEl.appendChild(imported);
+  }
+
+  elDialog = document.createElement("payment-dialog");
+  displayEl.appendChild(elDialog);
+  elPicker = elDialog.querySelector("address-picker.payer-related");
+
+  initialState = Object.assign({}, elDialog.requestStore.getState(), {
+    changesPrevented: false,
+    completionState: "initial",
+    orderDetailsShowing: false,
+  });
+});
+
+async function setup() {
+  // reset the store back to a known, default state
+  elDialog.requestStore.setState(deepClone(initialState));
+  await asyncElementRendered();
+}
+
+add_task(async function test_empty() {
+  await setup();
+
+  let {request, savedAddresses} = elPicker.requestStore.getState();
+  ok(!savedAddresses || !savedAddresses.length,
+     "Check initial state has no saved addresses");
+
+  let {paymentOptions} = request;
+  let payerRequested = paymentOptions.requestPayerName ||
+                   paymentOptions.requestPayerEmail ||
+                   paymentOptions.requestPayerPhone;
+  ok(!payerRequested, "Check initial state has no payer details requested");
+  ok(elPicker, "Check elPicker exists");
+  is(elPicker.dropdown.popupBox.children.length, 0, "Check dropdown is empty");
+  is(isVisible(elPicker), false, "The address-picker is not visible");
+});
+
+// paymentOptions properties are acurately reflected in the address-fields attribute
+add_task(async function test_visible_fields() {
+  await setup();
+  let requestStore = elPicker.requestStore;
+  setPaymentOptions(requestStore, {
+    requestPayerName: true,
+    requestPayerEmail: true,
+    requestPayerPhone: true,
+  });
+
+  requestStore.setState({
+    savedAddresses: SAVED_ADDRESSES,
+    selectedPayerAddress: "48bnds6854t",
+  });
+
+  await asyncElementRendered();
+
+  let visibleOptions = getVisiblePickerOptions(elPicker);
+  let visibleOption = visibleOptions[0];
+
+  is(elPicker.dropdown.popupBox.children.length, 2, "Check dropdown has 2 addresses");
+  is(visibleOptions.length, 1, "One option should be visible");
+  is(visibleOption.getAttribute("guid"), "48bnds6854t", "expected option is visible");
+
+  for (let fieldName of ["name", "email", "tel"]) {
+    let elem = visibleOption.querySelector(`.${fieldName}`);
+    ok(elem, `field ${fieldName} exists`);
+    ok(isVisible(elem), `field ${fieldName} is visible`);
+  }
+  ok(!isVisible(visibleOption.querySelector(".street-address")), "street-address is not visible");
+});
+
+add_task(async function test_selective_fields() {
+  await setup();
+  let requestStore = elPicker.requestStore;
+
+  requestStore.setState({
+    savedAddresses: SAVED_ADDRESSES,
+    selectedPayerAddress: "48bnds6854t",
+  });
+
+  let payerFieldVariations = [
+    {requestPayerName: true, requestPayerEmail: false, requestPayerPhone: false },
+    {requestPayerName: false, requestPayerEmail: true, requestPayerPhone: false },
+    {requestPayerName: false, requestPayerEmail: false, requestPayerPhone: true },
+    {requestPayerName: true, requestPayerEmail: true, requestPayerPhone: false },
+    {requestPayerName: false, requestPayerEmail: true, requestPayerPhone: true },
+    {requestPayerName: true, requestPayerEmail: false, requestPayerPhone: true },
+  ];
+
+  for (let payerFields of payerFieldVariations) {
+    setPaymentOptions(requestStore, payerFields);
+    await asyncElementRendered();
+
+    let visibleOption = getVisiblePickerOptions(elPicker)[0];
+    let elName = visibleOption.querySelector(".name");
+    let elEmail = visibleOption.querySelector(".email");
+    let elPhone = visibleOption.querySelector(".tel");
+
+    is(isVisible(elName), payerFields.requestPayerName,
+       "name field is correctly toggled");
+    is(isVisible(elEmail), payerFields.requestPayerEmail,
+       "email field is correctly toggled");
+    is(isVisible(elPhone), payerFields.requestPayerPhone,
+       "tel field is correctly toggled");
+  }
+});
+</script>
+
+</body>
+</html>