Bug 1440499 - Implement the payerName/payerEmail/payerPhone contact picker draft
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Fri, 23 Feb 2018 12:02:41 -0800
changeset 762277 f8292ea2edc01d54a34edd1485df81a6153ecb89
parent 761128 c50f5f846c2e6b4c8da1e80b6790926ad84c3450
child 762278 4f1b33b96dfae45b5c1cccb17d707c6915edef39
push id101115
push userbmo:sfoster@mozilla.com
push dateThu, 01 Mar 2018 23:33:37 +0000
bugs1440499
milestone60.0a1
Bug 1440499 - Implement the payerName/payerEmail/payerPhone contact picker MozReview-Commit-ID: GJbAT1ABlXd
toolkit/components/payments/content/paymentDialogWrapper.js
toolkit/components/payments/res/components/address-option.css
toolkit/components/payments/res/components/address-option.js
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
--- 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 profileStorage 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 = profileStorage.addresses.get(guid);
     if (!addressData) {
       throw new Error(`Shipping address not found: ${guid}`);
     }
 
@@ -373,32 +400,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-option[hide-address] {
+  grid-template-areas:
+    "name name"
+    "tel  email";
+}
+
 address-option > .name {
   grid-area: name;
 }
 
 address-option > .street-address {
   grid-area: street-address;
 }
 
@@ -35,12 +41,13 @@ 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-option[hide-address] > .street-address,
+.rich-select-selected-clone:not([hide-address]) > .email,
+.rich-select-selected-clone:not([hide-address]) > .tel {
   display: none;
 }
--- a/toolkit/components/payments/res/components/address-option.js
+++ b/toolkit/components/payments/res/components/address-option.js
@@ -21,16 +21,17 @@
 /* global ObservedPropertiesMixin, RichOption */
 
 class AddressOption extends ObservedPropertiesMixin(RichOption) {
   static get observedAttributes() {
     return RichOption.observedAttributes.concat([
       "address-level1",
       "address-level2",
       "country",
+      "hide-address",
       "email",
       "guid",
       "name",
       "postal-code",
       "street-address",
       "tel",
     ]);
   }
--- a/toolkit/components/payments/res/containers/address-picker.js
+++ b/toolkit/components/payments/res/containers/address-picker.js
@@ -21,26 +21,28 @@ class AddressPicker extends PaymentState
 
   connectedCallback() {
     this.appendChild(this.dropdown);
     super.connectedCallback();
   }
 
   render(state) {
     let {savedAddresses} = state;
+    let hideAddress = this.hasAttribute("hide-address"); // TODO
     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);
       }
+      optionEl.hideAddress = hideAddress;
       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,17 @@ 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._errorText = contents.querySelector("#error-text");
 
     this._disabledOverlay = contents.getElementById("disabled-overlay");
 
     this.appendChild(contents);
 
     super.connectedCallback();
   }
@@ -64,21 +65,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 +104,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 +147,26 @@ 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":
         break;
@@ -193,16 +207,20 @@ class PaymentDialog extends PaymentState
     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;
+    }
     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
@@ -142,16 +142,17 @@ let REQUEST_CONTACT_NO_SHIPPING = {
   },
 };
 
 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 orderDetailsLabel          "Order Details">
   <!ENTITY orderTotalLabel            "Total">
 ]>
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
@@ -62,27 +63,33 @@
       </div>
     </header>
 
     <div id="main-container">
       <section id="payment-summary">
         <h1>&paymentSummaryTitle;</h1>
 
         <section>
+          <div id="error-text"></div>
+
           <div class="shipping-related"
                id="shipping-type-label"
                data-shipping-address-label="&shippingAddressLabel;"
                data-delivery-address-label="&deliveryAddressLabel;"
                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 id="error-text"></div>
+
+          <div class="payer-related"><label>&payerLabel;</label></div>
+          <address-picker class="payer-related"
+                          hide-address=""
+                          selected-state-key="selectedPayerAddress"></address-picker>
         </section>
 
         <footer id="controls-container">
           <button id="cancel">&cancelPaymentButton.label;</button>
           <button id="pay"
                   data-initial-label="&approvePaymentButton.label;"
                   data-processing-label="&processingPaymentButton.label;"></button>
         </footer>