mirror of
https://github.com/Feodor2/Mypal68.git
synced 2025-06-18 14:55:44 -04:00
594 lines
20 KiB
JavaScript
594 lines
20 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/. */
|
|
|
|
import HandleEventMixin from "../mixins/HandleEventMixin.js";
|
|
import PaymentStateSubscriberMixin from "../mixins/PaymentStateSubscriberMixin.js";
|
|
import paymentRequest from "../paymentRequest.js";
|
|
|
|
import "../components/currency-amount.js";
|
|
import "../components/payment-request-page.js";
|
|
import "../components/accepted-cards.js";
|
|
import "./address-picker.js";
|
|
import "./address-form.js";
|
|
import "./basic-card-form.js";
|
|
import "./completion-error-page.js";
|
|
import "./order-details.js";
|
|
import "./payment-method-picker.js";
|
|
import "./shipping-option-picker.js";
|
|
|
|
/* import-globals-from ../unprivileged-fallbacks.js */
|
|
|
|
/**
|
|
* <payment-dialog></payment-dialog>
|
|
*
|
|
* Warning: Do not import this module from any other module as it will import
|
|
* everything else (see above) and ruin element independence. This can stop
|
|
* being exported once tests stop depending on it.
|
|
*/
|
|
|
|
export default class PaymentDialog extends HandleEventMixin(
|
|
PaymentStateSubscriberMixin(HTMLElement)
|
|
) {
|
|
constructor() {
|
|
super();
|
|
this._template = document.getElementById("payment-dialog-template");
|
|
this._cachedState = {};
|
|
}
|
|
|
|
connectedCallback() {
|
|
let contents = document.importNode(this._template.content, true);
|
|
this._hostNameEl = contents.querySelector("#host-name");
|
|
|
|
this._cancelButton = contents.querySelector("#cancel");
|
|
this._cancelButton.addEventListener("click", this.cancelRequest);
|
|
|
|
this._payButton = contents.querySelector("#pay");
|
|
this._payButton.addEventListener("click", this);
|
|
|
|
this._viewAllButton = contents.querySelector("#view-all");
|
|
this._viewAllButton.addEventListener("click", this);
|
|
|
|
this._mainContainer = contents.getElementById("main-container");
|
|
this._orderDetailsOverlay = contents.querySelector(
|
|
"#order-details-overlay"
|
|
);
|
|
|
|
this._shippingAddressPicker = contents.querySelector(
|
|
"address-picker.shipping-related"
|
|
);
|
|
this._shippingOptionPicker = contents.querySelector(
|
|
"shipping-option-picker"
|
|
);
|
|
this._shippingRelatedEls = contents.querySelectorAll(".shipping-related");
|
|
this._payerRelatedEls = contents.querySelectorAll(".payer-related");
|
|
this._payerAddressPicker = contents.querySelector(
|
|
"address-picker.payer-related"
|
|
);
|
|
this._paymentMethodPicker = contents.querySelector("payment-method-picker");
|
|
this._acceptedCardsList = contents.querySelector("accepted-cards");
|
|
this._manageText = contents.querySelector(".manage-text");
|
|
this._manageText.addEventListener("click", this);
|
|
|
|
this._header = contents.querySelector("header");
|
|
|
|
this._errorText = contents.querySelector("header > .page-error");
|
|
|
|
this._disabledOverlay = contents.getElementById("disabled-overlay");
|
|
|
|
this.appendChild(contents);
|
|
|
|
super.connectedCallback();
|
|
}
|
|
|
|
disconnectedCallback() {
|
|
this._cancelButton.removeEventListener("click", this.cancelRequest);
|
|
this._payButton.removeEventListener("click", this.pay);
|
|
this._viewAllButton.removeEventListener("click", this);
|
|
super.disconnectedCallback();
|
|
}
|
|
|
|
onClick(event) {
|
|
switch (event.currentTarget) {
|
|
case this._viewAllButton:
|
|
let orderDetailsShowing = !this.requestStore.getState()
|
|
.orderDetailsShowing;
|
|
this.requestStore.setState({ orderDetailsShowing });
|
|
break;
|
|
case this._payButton:
|
|
this.pay();
|
|
break;
|
|
case this._manageText:
|
|
if (event.target instanceof HTMLAnchorElement) {
|
|
this.openPreferences(event);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
openPreferences(event) {
|
|
paymentRequest.openPreferences();
|
|
event.preventDefault();
|
|
}
|
|
|
|
cancelRequest() {
|
|
paymentRequest.cancel();
|
|
}
|
|
|
|
pay() {
|
|
let state = this.requestStore.getState();
|
|
let {
|
|
selectedPayerAddress,
|
|
selectedPaymentCard,
|
|
selectedPaymentCardSecurityCode,
|
|
selectedShippingAddress,
|
|
} = state;
|
|
|
|
let data = {
|
|
selectedPaymentCardGUID: selectedPaymentCard,
|
|
selectedPaymentCardSecurityCode,
|
|
};
|
|
|
|
data.selectedShippingAddressGUID = state.request.paymentOptions
|
|
.requestShipping
|
|
? selectedShippingAddress
|
|
: null;
|
|
|
|
data.selectedPayerAddressGUID = this._isPayerRequested(
|
|
state.request.paymentOptions
|
|
)
|
|
? selectedPayerAddress
|
|
: null;
|
|
|
|
paymentRequest.pay(data);
|
|
}
|
|
|
|
/**
|
|
* Called when the selectedShippingAddress or its properties are changed.
|
|
* @param {string} shippingAddressGUID
|
|
*/
|
|
changeShippingAddress(shippingAddressGUID) {
|
|
// Clear shipping address merchant errors when the shipping address changes.
|
|
let request = Object.assign({}, this.requestStore.getState().request);
|
|
request.paymentDetails = Object.assign({}, request.paymentDetails);
|
|
request.paymentDetails.shippingAddressErrors = {};
|
|
this.requestStore.setState({ request });
|
|
|
|
paymentRequest.changeShippingAddress({
|
|
shippingAddressGUID,
|
|
});
|
|
}
|
|
|
|
changeShippingOption(optionID) {
|
|
paymentRequest.changeShippingOption({
|
|
optionID,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Called when the selectedPaymentCard or its relevant properties or billingAddress are changed.
|
|
* @param {string} selectedPaymentCardBillingAddressGUID
|
|
*/
|
|
changePaymentMethod(selectedPaymentCardBillingAddressGUID) {
|
|
// Clear paymentMethod merchant errors when the paymentMethod or billingAddress changes.
|
|
let request = Object.assign({}, this.requestStore.getState().request);
|
|
request.paymentDetails = Object.assign({}, request.paymentDetails);
|
|
request.paymentDetails.paymentMethodErrors = null;
|
|
this.requestStore.setState({ request });
|
|
|
|
paymentRequest.changePaymentMethod({
|
|
selectedPaymentCardBillingAddressGUID,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Called when the selectedPayerAddress or its relevant properties are changed.
|
|
* @param {string} payerAddressGUID
|
|
*/
|
|
changePayerAddress(payerAddressGUID) {
|
|
// Clear payer address merchant errors when the payer address changes.
|
|
let request = Object.assign({}, this.requestStore.getState().request);
|
|
request.paymentDetails = Object.assign({}, request.paymentDetails);
|
|
request.paymentDetails.payerErrors = {};
|
|
this.requestStore.setState({ request });
|
|
|
|
paymentRequest.changePayerAddress({
|
|
payerAddressGUID,
|
|
});
|
|
}
|
|
|
|
_isPayerRequested(paymentOptions) {
|
|
return (
|
|
paymentOptions.requestPayerName ||
|
|
paymentOptions.requestPayerEmail ||
|
|
paymentOptions.requestPayerPhone
|
|
);
|
|
}
|
|
|
|
_getAdditionalDisplayItems(state) {
|
|
let methodId = state.selectedPaymentCard;
|
|
let modifier = paymentRequest.getModifierForPaymentMethod(state, methodId);
|
|
if (modifier && modifier.additionalDisplayItems) {
|
|
return modifier.additionalDisplayItems;
|
|
}
|
|
return [];
|
|
}
|
|
|
|
_updateCompleteStatus(state) {
|
|
let { completeStatus } = state.request;
|
|
switch (completeStatus) {
|
|
case "fail":
|
|
case "timeout":
|
|
case "unknown":
|
|
state.page = {
|
|
id: `completion-${completeStatus}-error`,
|
|
};
|
|
state.changesPrevented = false;
|
|
break;
|
|
case "": {
|
|
// When we get a DOM update for an updateWith() or retry() the completeStatus
|
|
// is "" when we need to show non-final screens. Don't set the page as we
|
|
// may be on a form instead of payment-summary
|
|
state.changesPrevented = false;
|
|
break;
|
|
}
|
|
}
|
|
return state;
|
|
}
|
|
|
|
/**
|
|
* Set some state from the privileged parent process.
|
|
* Other elements that need to set state should use their own `this.requestStore.setState`
|
|
* method provided by the `PaymentStateSubscriberMixin`.
|
|
*
|
|
* @param {object} state - See `PaymentsStore.setState`
|
|
*/
|
|
// eslint-disable-next-line complexity
|
|
async setStateFromParent(state) {
|
|
let oldAddresses = paymentRequest.getAddresses(
|
|
this.requestStore.getState()
|
|
);
|
|
let oldBasicCards = paymentRequest.getBasicCards(
|
|
this.requestStore.getState()
|
|
);
|
|
if (state.request) {
|
|
state = this._updateCompleteStatus(state);
|
|
}
|
|
this.requestStore.setState(state);
|
|
|
|
// Check if any foreign-key constraints were invalidated.
|
|
state = this.requestStore.getState();
|
|
let {
|
|
selectedPayerAddress,
|
|
selectedPaymentCard,
|
|
selectedShippingAddress,
|
|
selectedShippingOption,
|
|
} = state;
|
|
let addresses = paymentRequest.getAddresses(state);
|
|
let { paymentOptions } = state.request;
|
|
|
|
if (paymentOptions.requestShipping) {
|
|
let shippingOptions = state.request.paymentDetails.shippingOptions;
|
|
let shippingAddress =
|
|
selectedShippingAddress && addresses[selectedShippingAddress];
|
|
let oldShippingAddress =
|
|
selectedShippingAddress && oldAddresses[selectedShippingAddress];
|
|
|
|
// Ensure `selectedShippingAddress` never refers to a deleted address.
|
|
// We also compare address timestamps to notify about changes
|
|
// made outside the payments UI.
|
|
if (shippingAddress) {
|
|
// invalidate the cached value if the address was modified
|
|
if (
|
|
oldShippingAddress &&
|
|
shippingAddress.guid == oldShippingAddress.guid &&
|
|
shippingAddress.timeLastModified !=
|
|
oldShippingAddress.timeLastModified
|
|
) {
|
|
delete this._cachedState.selectedShippingAddress;
|
|
}
|
|
} else if (selectedShippingAddress !== null) {
|
|
// null out the `selectedShippingAddress` property if it is undefined,
|
|
// or if the address it pointed to was removed from storage.
|
|
log.debug("resetting invalid/deleted shipping address");
|
|
this.requestStore.setState({
|
|
selectedShippingAddress: null,
|
|
});
|
|
}
|
|
|
|
// Ensure `selectedShippingOption` never refers to a deleted shipping option and
|
|
// matches the merchant's selected option if the user hasn't made a choice.
|
|
if (
|
|
shippingOptions &&
|
|
(!selectedShippingOption ||
|
|
!shippingOptions.find(opt => opt.id == selectedShippingOption))
|
|
) {
|
|
this._cachedState.selectedShippingOption = selectedShippingOption;
|
|
this.requestStore.setState({
|
|
// Use the DOM's computed selected shipping option:
|
|
selectedShippingOption: state.request.shippingOption,
|
|
});
|
|
}
|
|
}
|
|
|
|
let basicCards = paymentRequest.getBasicCards(state);
|
|
let oldPaymentMethod =
|
|
selectedPaymentCard && oldBasicCards[selectedPaymentCard];
|
|
let paymentMethod = selectedPaymentCard && basicCards[selectedPaymentCard];
|
|
if (
|
|
oldPaymentMethod &&
|
|
paymentMethod.guid == oldPaymentMethod.guid &&
|
|
paymentMethod.timeLastModified != oldPaymentMethod.timeLastModified
|
|
) {
|
|
delete this._cachedState.selectedPaymentCard;
|
|
} else {
|
|
// Changes to the billing address record don't change the `timeLastModified`
|
|
// on the card record so we have to check for changes to the address separately.
|
|
|
|
let billingAddressGUID =
|
|
paymentMethod && paymentMethod.billingAddressGUID;
|
|
let billingAddress = billingAddressGUID && addresses[billingAddressGUID];
|
|
let oldBillingAddress =
|
|
billingAddressGUID && oldAddresses[billingAddressGUID];
|
|
|
|
if (
|
|
oldBillingAddress &&
|
|
billingAddress &&
|
|
billingAddress.timeLastModified != oldBillingAddress.timeLastModified
|
|
) {
|
|
delete this._cachedState.selectedPaymentCard;
|
|
}
|
|
}
|
|
|
|
// Ensure `selectedPaymentCard` never refers to a deleted payment card.
|
|
if (selectedPaymentCard && !basicCards[selectedPaymentCard]) {
|
|
this.requestStore.setState({
|
|
selectedPaymentCard: null,
|
|
selectedPaymentCardSecurityCode: null,
|
|
});
|
|
}
|
|
|
|
if (this._isPayerRequested(state.request.paymentOptions)) {
|
|
let payerAddress =
|
|
selectedPayerAddress && addresses[selectedPayerAddress];
|
|
let oldPayerAddress =
|
|
selectedPayerAddress && oldAddresses[selectedPayerAddress];
|
|
|
|
if (
|
|
oldPayerAddress &&
|
|
payerAddress &&
|
|
((paymentOptions.requestPayerName &&
|
|
payerAddress.name != oldPayerAddress.name) ||
|
|
(paymentOptions.requestPayerEmail &&
|
|
payerAddress.email != oldPayerAddress.email) ||
|
|
(paymentOptions.requestPayerPhone &&
|
|
payerAddress.tel != oldPayerAddress.tel))
|
|
) {
|
|
// invalidate the cached value if the payer address fields were modified
|
|
delete this._cachedState.selectedPayerAddress;
|
|
}
|
|
|
|
// Ensure `selectedPayerAddress` never refers to a deleted address and refers
|
|
// to an address if one exists.
|
|
if (!addresses[selectedPayerAddress]) {
|
|
this.requestStore.setState({
|
|
selectedPayerAddress: Object.keys(addresses)[0] || null,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
_renderPayButton(state) {
|
|
let completeStatus = state.request.completeStatus;
|
|
switch (completeStatus) {
|
|
case "processing":
|
|
case "success":
|
|
case "unknown": {
|
|
this._payButton.disabled = true;
|
|
this._payButton.textContent = this._payButton.dataset[
|
|
completeStatus + "Label"
|
|
];
|
|
break;
|
|
}
|
|
case "": {
|
|
// initial/default state
|
|
this._payButton.textContent = this._payButton.dataset.label;
|
|
const INVALID_CLASS_NAME = "invalid-selected-option";
|
|
this._payButton.disabled =
|
|
(state.request.paymentOptions.requestShipping &&
|
|
(!this._shippingAddressPicker.selectedOption ||
|
|
this._shippingAddressPicker.classList.contains(
|
|
INVALID_CLASS_NAME
|
|
) ||
|
|
!this._shippingOptionPicker.selectedOption)) ||
|
|
(this._isPayerRequested(state.request.paymentOptions) &&
|
|
(!this._payerAddressPicker.selectedOption ||
|
|
this._payerAddressPicker.classList.contains(
|
|
INVALID_CLASS_NAME
|
|
))) ||
|
|
!this._paymentMethodPicker.securityCodeInput.isValid ||
|
|
!this._paymentMethodPicker.selectedOption ||
|
|
this._paymentMethodPicker.classList.contains(INVALID_CLASS_NAME) ||
|
|
state.changesPrevented;
|
|
break;
|
|
}
|
|
case "fail":
|
|
case "timeout": {
|
|
// pay button is hidden in fail/timeout states.
|
|
this._payButton.textContent = this._payButton.dataset.label;
|
|
this._payButton.disabled = true;
|
|
break;
|
|
}
|
|
default: {
|
|
throw new Error(`Invalid completeStatus: ${completeStatus}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
_renderPayerFields(state) {
|
|
let paymentOptions = state.request.paymentOptions;
|
|
let payerRequested = this._isPayerRequested(paymentOptions);
|
|
let payerAddressForm = this.querySelector(
|
|
"address-form[selected-state-key='selectedPayerAddress']"
|
|
);
|
|
|
|
for (let element of this._payerRelatedEls) {
|
|
element.hidden = !payerRequested;
|
|
}
|
|
|
|
if (payerRequested) {
|
|
let fieldNames = new Set();
|
|
if (paymentOptions.requestPayerName) {
|
|
fieldNames.add("name");
|
|
}
|
|
if (paymentOptions.requestPayerEmail) {
|
|
fieldNames.add("email");
|
|
}
|
|
if (paymentOptions.requestPayerPhone) {
|
|
fieldNames.add("tel");
|
|
}
|
|
let addressFields = [...fieldNames].join(" ");
|
|
this._payerAddressPicker.setAttribute("address-fields", addressFields);
|
|
if (payerAddressForm.form) {
|
|
payerAddressForm.form.dataset.extraRequiredFields = addressFields;
|
|
}
|
|
|
|
// For the payer picker we want to have a line break after the name field (#1)
|
|
// if all three fields are requested.
|
|
if (fieldNames.size == 3) {
|
|
this._payerAddressPicker.setAttribute("break-after-nth-field", 1);
|
|
} else {
|
|
this._payerAddressPicker.removeAttribute("break-after-nth-field");
|
|
}
|
|
} else {
|
|
this._payerAddressPicker.removeAttribute("address-fields");
|
|
}
|
|
}
|
|
|
|
stateChangeCallback(state) {
|
|
super.stateChangeCallback(state);
|
|
|
|
// Don't dispatch change events for initial selectedShipping* changes at initialization
|
|
// if requestShipping is false.
|
|
if (state.request.paymentOptions.requestShipping) {
|
|
if (
|
|
state.selectedShippingAddress !=
|
|
this._cachedState.selectedShippingAddress
|
|
) {
|
|
this.changeShippingAddress(state.selectedShippingAddress);
|
|
}
|
|
|
|
if (
|
|
state.selectedShippingOption != this._cachedState.selectedShippingOption
|
|
) {
|
|
this.changeShippingOption(state.selectedShippingOption);
|
|
}
|
|
}
|
|
|
|
let selectedPaymentCard = state.selectedPaymentCard;
|
|
let basicCards = paymentRequest.getBasicCards(state);
|
|
let billingAddressGUID = (basicCards[selectedPaymentCard] || {})
|
|
.billingAddressGUID;
|
|
if (
|
|
selectedPaymentCard != this._cachedState.selectedPaymentCard &&
|
|
billingAddressGUID
|
|
) {
|
|
// Update _cachedState to prevent an infinite loop when changePaymentMethod updates state.
|
|
this._cachedState.selectedPaymentCard = state.selectedPaymentCard;
|
|
this.changePaymentMethod(billingAddressGUID);
|
|
}
|
|
|
|
if (this._isPayerRequested(state.request.paymentOptions)) {
|
|
if (
|
|
state.selectedPayerAddress != this._cachedState.selectedPayerAddress
|
|
) {
|
|
this.changePayerAddress(state.selectedPayerAddress);
|
|
}
|
|
}
|
|
|
|
this._cachedState.selectedShippingAddress = state.selectedShippingAddress;
|
|
this._cachedState.selectedShippingOption = state.selectedShippingOption;
|
|
this._cachedState.selectedPayerAddress = state.selectedPayerAddress;
|
|
}
|
|
|
|
render(state) {
|
|
let request = state.request;
|
|
let paymentDetails = request.paymentDetails;
|
|
this._hostNameEl.textContent = request.topLevelPrincipal.URI.displayHost;
|
|
|
|
let displayItems = request.paymentDetails.displayItems || [];
|
|
let additionalItems = this._getAdditionalDisplayItems(state);
|
|
this._viewAllButton.hidden =
|
|
!displayItems.length && !additionalItems.length;
|
|
|
|
let shippingType = state.request.paymentOptions.shippingType || "shipping";
|
|
let addressPickerLabel = this._shippingAddressPicker.dataset[
|
|
shippingType + "AddressLabel"
|
|
];
|
|
this._shippingAddressPicker.setAttribute("label", addressPickerLabel);
|
|
let optionPickerLabel = this._shippingOptionPicker.dataset[
|
|
shippingType + "OptionsLabel"
|
|
];
|
|
this._shippingOptionPicker.setAttribute("label", optionPickerLabel);
|
|
|
|
let shippingAddressForm = this.querySelector(
|
|
"address-form[selected-state-key='selectedShippingAddress']"
|
|
);
|
|
shippingAddressForm.dataset.titleAdd = this.dataset[
|
|
shippingType + "AddressTitleAdd"
|
|
];
|
|
shippingAddressForm.dataset.titleEdit = this.dataset[
|
|
shippingType + "AddressTitleEdit"
|
|
];
|
|
|
|
let totalItem = paymentRequest.getTotalItem(state);
|
|
let totalAmountEl = this.querySelector("#total > currency-amount");
|
|
totalAmountEl.value = totalItem.amount.value;
|
|
totalAmountEl.currency = totalItem.amount.currency;
|
|
|
|
// Show the total header on the address and basic card pages only during
|
|
// on-boarding(FTU) and on the payment summary page.
|
|
this._header.hidden =
|
|
!state.page.onboardingWizard && state.page.id != "payment-summary";
|
|
|
|
this._orderDetailsOverlay.hidden = !state.orderDetailsShowing;
|
|
let genericError = "";
|
|
if (
|
|
this._shippingAddressPicker.selectedOption &&
|
|
(!request.paymentDetails.shippingOptions ||
|
|
!request.paymentDetails.shippingOptions.length)
|
|
) {
|
|
genericError = this._errorText.dataset[shippingType + "GenericError"];
|
|
}
|
|
this._errorText.textContent = paymentDetails.error || genericError;
|
|
|
|
let paymentOptions = request.paymentOptions;
|
|
for (let element of this._shippingRelatedEls) {
|
|
element.hidden = !paymentOptions.requestShipping;
|
|
}
|
|
|
|
this._renderPayerFields(state);
|
|
|
|
let isMac = /mac/i.test(navigator.platform);
|
|
for (let manageTextEl of this._manageText.children) {
|
|
manageTextEl.hidden = manageTextEl.dataset.os == "mac" ? !isMac : isMac;
|
|
let link = manageTextEl.querySelector("a");
|
|
// The href is only set to be exposed to accessibility tools so users know what will open.
|
|
// The actual opening happens from the click event listener.
|
|
link.href = "about:preferences#privacy-form-autofill";
|
|
}
|
|
|
|
this._renderPayButton(state);
|
|
|
|
for (let page of this._mainContainer.querySelectorAll(":scope > .page")) {
|
|
page.hidden = state.page.id != page.id;
|
|
}
|
|
|
|
this.toggleAttribute("changes-prevented", state.changesPrevented);
|
|
this.setAttribute("complete-status", request.completeStatus);
|
|
this._disabledOverlay.hidden = !state.changesPrevented;
|
|
}
|
|
}
|
|
|
|
customElements.define("payment-dialog", PaymentDialog);
|