forked from mirrors/gecko-dev
		
	 9d60e0d578
			
		
	
	
		9d60e0d578
		
	
	
	
	
		
			
			Differential Revision: https://phabricator.services.mozilla.com/D45629 --HG-- extra : moz-landing-system : lando
		
			
				
	
	
		
			356 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			356 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| /**
 | |
|  * Loaded in the unprivileged frame of each payment dialog.
 | |
|  *
 | |
|  * Communicates with privileged code via DOM Events.
 | |
|  */
 | |
| 
 | |
| /* import-globals-from unprivileged-fallbacks.js */
 | |
| 
 | |
| var paymentRequest = {
 | |
|   _nextMessageID: 1,
 | |
|   domReadyPromise: null,
 | |
| 
 | |
|   init() {
 | |
|     // listen to content
 | |
|     window.addEventListener("paymentChromeToContent", this);
 | |
| 
 | |
|     window.addEventListener("keydown", this);
 | |
| 
 | |
|     this.domReadyPromise = new Promise(function dcl(resolve) {
 | |
|       window.addEventListener("DOMContentLoaded", resolve, { once: true });
 | |
|     }).then(this.handleEvent.bind(this));
 | |
| 
 | |
|     // This scope is now ready to listen to the initialization data
 | |
|     this.sendMessageToChrome("initializeRequest");
 | |
|   },
 | |
| 
 | |
|   handleEvent(event) {
 | |
|     switch (event.type) {
 | |
|       case "DOMContentLoaded": {
 | |
|         this.onPaymentRequestLoad();
 | |
|         break;
 | |
|       }
 | |
|       case "keydown": {
 | |
|         if (event.code != "KeyD" || !event.altKey || !event.ctrlKey) {
 | |
|           break;
 | |
|         }
 | |
|         this.toggleDebuggingConsole();
 | |
|         break;
 | |
|       }
 | |
|       case "unload": {
 | |
|         this.onPaymentRequestUnload();
 | |
|         break;
 | |
|       }
 | |
|       case "paymentChromeToContent": {
 | |
|         this.onChromeToContent(event);
 | |
|         break;
 | |
|       }
 | |
|       default: {
 | |
|         throw new Error("Unexpected event type");
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * @param {string} messageType
 | |
|    * @param {[object]} detail
 | |
|    * @returns {number} message ID to be able to identify a reply (where applicable).
 | |
|    */
 | |
|   sendMessageToChrome(messageType, detail = {}) {
 | |
|     let messageID = this._nextMessageID++;
 | |
|     log.debug("sendMessageToChrome:", messageType, messageID, detail);
 | |
|     let event = new CustomEvent("paymentContentToChrome", {
 | |
|       bubbles: true,
 | |
|       detail: Object.assign(
 | |
|         {
 | |
|           messageType,
 | |
|           messageID,
 | |
|         },
 | |
|         detail
 | |
|       ),
 | |
|     });
 | |
|     document.dispatchEvent(event);
 | |
|     return messageID;
 | |
|   },
 | |
| 
 | |
|   toggleDebuggingConsole() {
 | |
|     let debuggingConsole = document.getElementById("debugging-console");
 | |
|     if (debuggingConsole.hidden && !debuggingConsole.src) {
 | |
|       debuggingConsole.src = "debugging.html";
 | |
|     }
 | |
|     debuggingConsole.hidden = !debuggingConsole.hidden;
 | |
|   },
 | |
| 
 | |
|   onChromeToContent({ detail }) {
 | |
|     let { messageType } = detail;
 | |
|     log.debug("onChromeToContent:", messageType);
 | |
| 
 | |
|     switch (messageType) {
 | |
|       case "responseSent": {
 | |
|         let { request } = document
 | |
|           .querySelector("payment-dialog")
 | |
|           .requestStore.getState();
 | |
|         document.querySelector("payment-dialog").requestStore.setState({
 | |
|           changesPrevented: true,
 | |
|           request: Object.assign({}, request, { completeStatus: "processing" }),
 | |
|         });
 | |
|         break;
 | |
|       }
 | |
|       case "showPaymentRequest": {
 | |
|         this.onShowPaymentRequest(detail);
 | |
|         break;
 | |
|       }
 | |
|       case "updateState": {
 | |
|         document.querySelector("payment-dialog").setStateFromParent(detail);
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   onPaymentRequestLoad() {
 | |
|     log.debug("onPaymentRequestLoad");
 | |
|     window.addEventListener("unload", this, { once: true });
 | |
| 
 | |
|     // Automatically show the debugging console if loaded with a truthy `debug` query parameter.
 | |
|     if (new URLSearchParams(location.search).get("debug")) {
 | |
|       this.toggleDebuggingConsole();
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   async onShowPaymentRequest(detail) {
 | |
|     // Handle getting called before the DOM is ready.
 | |
|     log.debug("onShowPaymentRequest:", detail);
 | |
|     await this.domReadyPromise;
 | |
| 
 | |
|     log.debug("onShowPaymentRequest: domReadyPromise resolved");
 | |
|     log.debug("onShowPaymentRequest, isPrivate?", detail.isPrivate);
 | |
| 
 | |
|     let paymentDialog = document.querySelector("payment-dialog");
 | |
|     let state = {
 | |
|       request: detail.request,
 | |
|       savedAddresses: detail.savedAddresses,
 | |
|       savedBasicCards: detail.savedBasicCards,
 | |
|       // Temp records can exist upon a reload during development.
 | |
|       tempAddresses: detail.tempAddresses,
 | |
|       tempBasicCards: detail.tempBasicCards,
 | |
|       isPrivate: detail.isPrivate,
 | |
|       page: {
 | |
|         id: "payment-summary",
 | |
|       },
 | |
|     };
 | |
| 
 | |
|     let hasSavedAddresses = !!Object.keys(this.getAddresses(state)).length;
 | |
|     let hasSavedCards = !!Object.keys(this.getBasicCards(state)).length;
 | |
|     let shippingRequested = state.request.paymentOptions.requestShipping;
 | |
| 
 | |
|     // Onboarding wizard flow.
 | |
|     if (!hasSavedAddresses && shippingRequested) {
 | |
|       state.page = {
 | |
|         id: "shipping-address-page",
 | |
|         onboardingWizard: true,
 | |
|       };
 | |
| 
 | |
|       state["shipping-address-page"] = {
 | |
|         guid: null,
 | |
|       };
 | |
|     } else if (!hasSavedAddresses && !hasSavedCards) {
 | |
|       state.page = {
 | |
|         id: "billing-address-page",
 | |
|         onboardingWizard: true,
 | |
|       };
 | |
| 
 | |
|       state["billing-address-page"] = {
 | |
|         guid: null,
 | |
|       };
 | |
|     } else if (!hasSavedCards) {
 | |
|       state.page = {
 | |
|         id: "basic-card-page",
 | |
|         onboardingWizard: true,
 | |
|       };
 | |
|       state["basic-card-page"] = {
 | |
|         selectedStateKey: "selectedPaymentCard",
 | |
|       };
 | |
|     }
 | |
| 
 | |
|     await paymentDialog.setStateFromParent(state);
 | |
| 
 | |
|     this.sendMessageToChrome("paymentDialogReady");
 | |
|   },
 | |
| 
 | |
|   openPreferences() {
 | |
|     this.sendMessageToChrome("openPreferences");
 | |
|   },
 | |
| 
 | |
|   cancel() {
 | |
|     this.sendMessageToChrome("paymentCancel");
 | |
|   },
 | |
| 
 | |
|   pay(data) {
 | |
|     this.sendMessageToChrome("pay", data);
 | |
|   },
 | |
| 
 | |
|   closeDialog() {
 | |
|     this.sendMessageToChrome("closeDialog");
 | |
|   },
 | |
| 
 | |
|   changePaymentMethod(data) {
 | |
|     this.sendMessageToChrome("changePaymentMethod", data);
 | |
|   },
 | |
| 
 | |
|   changeShippingAddress(data) {
 | |
|     this.sendMessageToChrome("changeShippingAddress", data);
 | |
|   },
 | |
| 
 | |
|   changeShippingOption(data) {
 | |
|     this.sendMessageToChrome("changeShippingOption", data);
 | |
|   },
 | |
| 
 | |
|   changePayerAddress(data) {
 | |
|     this.sendMessageToChrome("changePayerAddress", data);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Add/update an autofill storage record.
 | |
|    *
 | |
|    * If the the `guid` argument is provided update the record; otherwise, add it.
 | |
|    * @param {string} collectionName The autofill collection that record belongs to.
 | |
|    * @param {object} record The autofill record to add/update
 | |
|    * @param {string} [guid] The guid of the autofill record to update
 | |
|    * @returns {Promise} when the update response is received
 | |
|    */
 | |
|   updateAutofillRecord(collectionName, record, guid) {
 | |
|     return new Promise((resolve, reject) => {
 | |
|       let messageID = this.sendMessageToChrome("updateAutofillRecord", {
 | |
|         collectionName,
 | |
|         guid,
 | |
|         record,
 | |
|       });
 | |
| 
 | |
|       window.addEventListener("paymentChromeToContent", function onMsg({
 | |
|         detail,
 | |
|       }) {
 | |
|         if (
 | |
|           detail.messageType != "updateAutofillRecord:Response" ||
 | |
|           detail.messageID != messageID
 | |
|         ) {
 | |
|           return;
 | |
|         }
 | |
|         log.debug("updateAutofillRecord: response:", detail);
 | |
|         window.removeEventListener("paymentChromeToContent", onMsg);
 | |
|         document
 | |
|           .querySelector("payment-dialog")
 | |
|           .setStateFromParent(detail.stateChange);
 | |
|         if (detail.error) {
 | |
|           reject(detail);
 | |
|         } else {
 | |
|           resolve(detail);
 | |
|         }
 | |
|       });
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * @param {object} state object representing the UI state
 | |
|    * @param {string} selectedMethodID (GUID) uniquely identifying the selected payment method
 | |
|    * @returns {object?} the applicable modifier for the payment method
 | |
|    */
 | |
|   getModifierForPaymentMethod(state, selectedMethodID) {
 | |
|     let basicCards = this.getBasicCards(state);
 | |
|     let selectedMethod = basicCards[selectedMethodID] || null;
 | |
|     if (selectedMethod && selectedMethod.methodName !== "basic-card") {
 | |
|       throw new Error(
 | |
|         `${selectedMethod.methodName} (${selectedMethodID}) ` +
 | |
|           `is not a supported payment method`
 | |
|       );
 | |
|     }
 | |
|     let modifiers = state.request.paymentDetails.modifiers;
 | |
|     if (!selectedMethod || !modifiers || !modifiers.length) {
 | |
|       return null;
 | |
|     }
 | |
|     let appliedModifier = modifiers.find(modifier => {
 | |
|       // take the first matching modifier
 | |
|       if (
 | |
|         modifier.supportedMethods &&
 | |
|         modifier.supportedMethods != selectedMethod.methodName
 | |
|       ) {
 | |
|         return false;
 | |
|       }
 | |
|       let supportedNetworks =
 | |
|         (modifier.data && modifier.data.supportedNetworks) || [];
 | |
|       return (
 | |
|         !supportedNetworks.length ||
 | |
|         supportedNetworks.includes(selectedMethod["cc-type"])
 | |
|       );
 | |
|     });
 | |
|     return appliedModifier || null;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * @param {object} state object representing the UI state
 | |
|    * @returns {object} in the shape of `nsIPaymentItem` representing the total
 | |
|    *                   that applies to the selected payment method.
 | |
|    */
 | |
|   getTotalItem(state) {
 | |
|     let methodID = state.selectedPaymentCard;
 | |
|     if (methodID) {
 | |
|       let modifier = paymentRequest.getModifierForPaymentMethod(
 | |
|         state,
 | |
|         methodID
 | |
|       );
 | |
|       if (modifier && modifier.hasOwnProperty("total")) {
 | |
|         return modifier.total;
 | |
|       }
 | |
|     }
 | |
|     return state.request.paymentDetails.totalItem;
 | |
|   },
 | |
| 
 | |
|   onPaymentRequestUnload() {
 | |
|     // remove listeners that may be used multiple times here
 | |
|     window.removeEventListener("paymentChromeToContent", this);
 | |
|   },
 | |
| 
 | |
|   _sortObjectsByTimeLastUsed(objects) {
 | |
|     let sortedValues = Object.values(objects).sort((a, b) => {
 | |
|       let aLastUsed = a.timeLastUsed || a.timeLastModified;
 | |
|       let bLastUsed = b.timeLastUsed || b.timeLastModified;
 | |
|       return bLastUsed - aLastUsed;
 | |
|     });
 | |
|     let sortedObjects = {};
 | |
|     for (let obj of sortedValues) {
 | |
|       sortedObjects[obj.guid] = obj;
 | |
|     }
 | |
|     return sortedObjects;
 | |
|   },
 | |
| 
 | |
|   getAddresses(state) {
 | |
|     let addresses = Object.assign(
 | |
|       {},
 | |
|       state.savedAddresses,
 | |
|       state.tempAddresses
 | |
|     );
 | |
|     return this._sortObjectsByTimeLastUsed(addresses);
 | |
|   },
 | |
| 
 | |
|   getBasicCards(state) {
 | |
|     let cards = Object.assign({}, state.savedBasicCards, state.tempBasicCards);
 | |
|     return this._sortObjectsByTimeLastUsed(cards);
 | |
|   },
 | |
| 
 | |
|   maybeCreateFieldErrorElement(container) {
 | |
|     let span = container.querySelector(".error-text");
 | |
|     if (!span) {
 | |
|       span = document.createElement("span");
 | |
|       span.className = "error-text";
 | |
|       container.appendChild(span);
 | |
|     }
 | |
|     return span;
 | |
|   },
 | |
| };
 | |
| 
 | |
| paymentRequest.init();
 | |
| 
 | |
| export default paymentRequest;
 |