mirror of
https://github.com/Feodor2/Mypal68.git
synced 2025-06-18 14:55:44 -04:00
927 lines
27 KiB
JavaScript
927 lines
27 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/. */
|
|
|
|
/**
|
|
* Runs in the privileged outer dialog. Each dialog loads this script in its
|
|
* own scope.
|
|
*/
|
|
|
|
/* exported paymentDialogWrapper */
|
|
|
|
"use strict";
|
|
|
|
const paymentSrv = Cc[
|
|
"@mozilla.org/dom/payments/payment-request-service;1"
|
|
].getService(Ci.nsIPaymentRequestService);
|
|
|
|
const paymentUISrv = Cc[
|
|
"@mozilla.org/dom/payments/payment-ui-service;1"
|
|
].getService(Ci.nsIPaymentUIService);
|
|
|
|
const { AppConstants } = ChromeUtils.import(
|
|
"resource://gre/modules/AppConstants.jsm"
|
|
);
|
|
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
const { XPCOMUtils } = ChromeUtils.import(
|
|
"resource://gre/modules/XPCOMUtils.jsm"
|
|
);
|
|
|
|
ChromeUtils.defineModuleGetter(
|
|
this,
|
|
"BrowserWindowTracker",
|
|
"resource:///modules/BrowserWindowTracker.jsm"
|
|
);
|
|
ChromeUtils.defineModuleGetter(
|
|
this,
|
|
"FormAutofillUtils",
|
|
"resource://formautofill/FormAutofillUtils.jsm"
|
|
);
|
|
ChromeUtils.defineModuleGetter(
|
|
this,
|
|
"OSKeyStore",
|
|
"resource://formautofill/OSKeyStore.jsm"
|
|
);
|
|
ChromeUtils.defineModuleGetter(
|
|
this,
|
|
"PrivateBrowsingUtils",
|
|
"resource://gre/modules/PrivateBrowsingUtils.jsm"
|
|
);
|
|
|
|
XPCOMUtils.defineLazyGetter(this, "formAutofillStorage", () => {
|
|
let storage;
|
|
try {
|
|
storage = ChromeUtils.import(
|
|
"resource://formautofill/FormAutofillStorage.jsm",
|
|
{}
|
|
).formAutofillStorage;
|
|
storage.initialize();
|
|
} catch (ex) {
|
|
storage = null;
|
|
Cu.reportError(ex);
|
|
}
|
|
|
|
return storage;
|
|
});
|
|
|
|
XPCOMUtils.defineLazyGetter(this, "reauthPasswordPromptMessage", () => {
|
|
const brandShortName = FormAutofillUtils.brandBundle.GetStringFromName(
|
|
"brandShortName"
|
|
);
|
|
return FormAutofillUtils.stringBundle.formatStringFromName(
|
|
`useCreditCardPasswordPrompt.${AppConstants.platform}`,
|
|
[brandShortName]
|
|
);
|
|
});
|
|
|
|
/**
|
|
* Temporary/transient storage for address and credit card records
|
|
*
|
|
* Implements a subset of the FormAutofillStorage collection class interface, and delegates to
|
|
* those classes for some utility methods
|
|
*/
|
|
class TempCollection {
|
|
constructor(type, data = {}) {
|
|
/**
|
|
* The name of the collection. e.g. 'addresses' or 'creditCards'
|
|
* Used to access methods from the FormAutofillStorage collections
|
|
*/
|
|
this._type = type;
|
|
this._data = data;
|
|
}
|
|
|
|
get _formAutofillCollection() {
|
|
// lazy getter for the formAutofill collection - to resolve on first access
|
|
Object.defineProperty(this, "_formAutofillCollection", {
|
|
value: formAutofillStorage[this._type],
|
|
writable: false,
|
|
configurable: true,
|
|
});
|
|
return this._formAutofillCollection;
|
|
}
|
|
|
|
get(guid) {
|
|
return this._data[guid];
|
|
}
|
|
|
|
async update(guid, record, preserveOldProperties) {
|
|
let recordToSave = Object.assign(
|
|
preserveOldProperties ? this._data[guid] : {},
|
|
record
|
|
);
|
|
await this._formAutofillCollection.computeFields(recordToSave);
|
|
return (this._data[guid] = recordToSave);
|
|
}
|
|
|
|
async add(record) {
|
|
let guid = "temp-" + Math.abs((Math.random() * 0xffffffff) | 0);
|
|
let timeLastModified = Date.now();
|
|
let recordToSave = Object.assign({ guid, timeLastModified }, record);
|
|
await this._formAutofillCollection.computeFields(recordToSave);
|
|
this._data[guid] = recordToSave;
|
|
return guid;
|
|
}
|
|
|
|
getAll() {
|
|
return this._data;
|
|
}
|
|
}
|
|
|
|
var paymentDialogWrapper = {
|
|
componentsLoaded: new Map(),
|
|
frameWeakRef: null,
|
|
mm: null,
|
|
request: null,
|
|
temporaryStore: null,
|
|
|
|
QueryInterface: ChromeUtils.generateQI([
|
|
Ci.nsIObserver,
|
|
Ci.nsISupportsWeakReference,
|
|
]),
|
|
|
|
/**
|
|
* @param {string} guid
|
|
* @returns {object} containing only the requested payer values.
|
|
*/
|
|
async _convertProfileAddressToPayerData(guid) {
|
|
let addressData =
|
|
this.temporaryStore.addresses.get(guid) ||
|
|
(await formAutofillStorage.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;
|
|
},
|
|
|
|
/**
|
|
* @param {string} guid
|
|
* @returns {nsIPaymentAddress}
|
|
*/
|
|
async _convertProfileAddressToPaymentAddress(guid) {
|
|
let addressData =
|
|
this.temporaryStore.addresses.get(guid) ||
|
|
(await formAutofillStorage.addresses.get(guid));
|
|
if (!addressData) {
|
|
throw new Error(`Address not found: ${guid}`);
|
|
}
|
|
|
|
let address = this.createPaymentAddress({
|
|
addressLines: addressData["street-address"].split("\n"),
|
|
city: addressData["address-level2"],
|
|
country: addressData.country,
|
|
dependentLocality: addressData["address-level3"],
|
|
organization: addressData.organization,
|
|
phone: addressData.tel,
|
|
postalCode: addressData["postal-code"],
|
|
recipient: addressData.name,
|
|
region: addressData["address-level1"],
|
|
// TODO (bug 1474905), The regionCode will be available when bug 1474905 is fixed
|
|
// and the region text box is changed to a dropdown with the regionCode being the
|
|
// value of the option and the region being the label for the option.
|
|
// A regionCode should be either the empty string or one to three code points
|
|
// that represent a region as the code element of an [ISO3166-2] country subdivision
|
|
// name (i.e., the characters after the hyphen in an ISO3166-2 country subdivision
|
|
// code element, such as "CA" for the state of California in the USA, or "11" for
|
|
// the Lisbon district of Portugal).
|
|
regionCode: "",
|
|
});
|
|
|
|
return address;
|
|
},
|
|
|
|
/**
|
|
* @param {string} guid The GUID of the basic card record from storage.
|
|
* @param {string} cardSecurityCode The associated card security code (CVV/CCV/etc.)
|
|
* @throws If there is an error decrypting
|
|
* @returns {nsIBasicCardResponseData?} returns response data or null (if the
|
|
* master password dialog was cancelled);
|
|
*/
|
|
async _convertProfileBasicCardToPaymentMethodData(guid, cardSecurityCode) {
|
|
let cardData =
|
|
this.temporaryStore.creditCards.get(guid) ||
|
|
(await formAutofillStorage.creditCards.get(guid));
|
|
if (!cardData) {
|
|
throw new Error(`Basic card not found in storage: ${guid}`);
|
|
}
|
|
|
|
let cardNumber;
|
|
try {
|
|
cardNumber = await OSKeyStore.decrypt(
|
|
cardData["cc-number-encrypted"],
|
|
reauthPasswordPromptMessage
|
|
);
|
|
} catch (ex) {
|
|
if (ex.result != Cr.NS_ERROR_ABORT) {
|
|
throw ex;
|
|
}
|
|
// User canceled master password entry
|
|
return null;
|
|
}
|
|
|
|
let billingAddressGUID = cardData.billingAddressGUID;
|
|
let billingAddress;
|
|
try {
|
|
billingAddress = await this._convertProfileAddressToPaymentAddress(
|
|
billingAddressGUID
|
|
);
|
|
} catch (ex) {
|
|
// The referenced address may not exist if it was deleted or hasn't yet synced to this profile
|
|
Cu.reportError(ex);
|
|
}
|
|
let methodData = this.createBasicCardResponseData({
|
|
cardholderName: cardData["cc-name"],
|
|
cardNumber,
|
|
expiryMonth: cardData["cc-exp-month"].toString().padStart(2, "0"),
|
|
expiryYear: cardData["cc-exp-year"].toString(),
|
|
cardSecurityCode,
|
|
billingAddress,
|
|
});
|
|
|
|
return methodData;
|
|
},
|
|
|
|
init(requestId, frame) {
|
|
if (!requestId || typeof requestId != "string") {
|
|
throw new Error("Invalid PaymentRequest ID");
|
|
}
|
|
|
|
// The Request object returned by the Payment Service is live and
|
|
// will automatically get updated if event.updateWith is used.
|
|
this.request = paymentSrv.getPaymentRequestById(requestId);
|
|
|
|
if (!this.request) {
|
|
throw new Error(`PaymentRequest not found: ${requestId}`);
|
|
}
|
|
|
|
this._attachToFrame(frame);
|
|
this.mm.loadFrameScript(
|
|
"chrome://payments/content/paymentDialogFrameScript.js",
|
|
true
|
|
);
|
|
// Until we have bug 1446164 and bug 1407418 we use form autofill's temporary
|
|
// shim for data-localization* attributes.
|
|
this.mm.loadFrameScript("chrome://formautofill/content/l10n.js", true);
|
|
frame.setAttribute("src", "resource://payments/paymentRequest.xhtml");
|
|
|
|
this.temporaryStore = {
|
|
addresses: new TempCollection("addresses"),
|
|
creditCards: new TempCollection("creditCards"),
|
|
};
|
|
},
|
|
|
|
uninit() {
|
|
try {
|
|
Services.obs.removeObserver(this, "message-manager-close");
|
|
Services.obs.removeObserver(this, "formautofill-storage-changed");
|
|
} catch (ex) {
|
|
// Observers may not have been added yet
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Code here will be re-run at various times, e.g. initial show and
|
|
* when a tab is detached to a different window.
|
|
*
|
|
* Code that should only run once belongs in `init`.
|
|
* Code to only run upon detaching should be in `changeAttachedFrame`.
|
|
*
|
|
* @param {Element} frame
|
|
*/
|
|
_attachToFrame(frame) {
|
|
this.frameWeakRef = Cu.getWeakReference(frame);
|
|
this.mm = frame.frameLoader.messageManager;
|
|
this.mm.addMessageListener("paymentContentToChrome", this);
|
|
Services.obs.addObserver(this, "message-manager-close", true);
|
|
},
|
|
|
|
/**
|
|
* Called only when a frame is changed from one to another.
|
|
*
|
|
* @param {Element} frame
|
|
*/
|
|
changeAttachedFrame(frame) {
|
|
this.mm.removeMessageListener("paymentContentToChrome", this);
|
|
this._attachToFrame(frame);
|
|
// This isn't in `attachToFrame` because we only want to do it once we've sent records.
|
|
Services.obs.addObserver(this, "formautofill-storage-changed", true);
|
|
},
|
|
|
|
createShowResponse({
|
|
acceptStatus,
|
|
methodName = "",
|
|
methodData = null,
|
|
payerName = "",
|
|
payerEmail = "",
|
|
payerPhone = "",
|
|
}) {
|
|
let showResponse = this.createComponentInstance(
|
|
Ci.nsIPaymentShowActionResponse
|
|
);
|
|
|
|
showResponse.init(
|
|
this.request.requestId,
|
|
acceptStatus,
|
|
methodName,
|
|
methodData,
|
|
payerName,
|
|
payerEmail,
|
|
payerPhone
|
|
);
|
|
return showResponse;
|
|
},
|
|
|
|
createBasicCardResponseData({
|
|
cardholderName = "",
|
|
cardNumber,
|
|
expiryMonth = "",
|
|
expiryYear = "",
|
|
cardSecurityCode = "",
|
|
billingAddress = null,
|
|
}) {
|
|
const basicCardResponseData = Cc[
|
|
"@mozilla.org/dom/payments/basiccard-response-data;1"
|
|
].createInstance(Ci.nsIBasicCardResponseData);
|
|
basicCardResponseData.initData(
|
|
cardholderName,
|
|
cardNumber,
|
|
expiryMonth,
|
|
expiryYear,
|
|
cardSecurityCode,
|
|
billingAddress
|
|
);
|
|
return basicCardResponseData;
|
|
},
|
|
|
|
createPaymentAddress({
|
|
addressLines = [],
|
|
city = "",
|
|
country = "",
|
|
dependentLocality = "",
|
|
organization = "",
|
|
postalCode = "",
|
|
phone = "",
|
|
recipient = "",
|
|
region = "",
|
|
regionCode = "",
|
|
sortingCode = "",
|
|
}) {
|
|
const paymentAddress = Cc[
|
|
"@mozilla.org/dom/payments/payment-address;1"
|
|
].createInstance(Ci.nsIPaymentAddress);
|
|
const addressLine = Cc["@mozilla.org/array;1"].createInstance(
|
|
Ci.nsIMutableArray
|
|
);
|
|
for (let line of addressLines) {
|
|
const address = Cc["@mozilla.org/supports-string;1"].createInstance(
|
|
Ci.nsISupportsString
|
|
);
|
|
address.data = line;
|
|
addressLine.appendElement(address);
|
|
}
|
|
paymentAddress.init(
|
|
country,
|
|
addressLine,
|
|
region,
|
|
regionCode,
|
|
city,
|
|
dependentLocality,
|
|
postalCode,
|
|
sortingCode,
|
|
organization,
|
|
recipient,
|
|
phone
|
|
);
|
|
return paymentAddress;
|
|
},
|
|
|
|
createComponentInstance(componentInterface) {
|
|
let componentName;
|
|
switch (componentInterface) {
|
|
case Ci.nsIPaymentShowActionResponse: {
|
|
componentName =
|
|
"@mozilla.org/dom/payments/payment-show-action-response;1";
|
|
break;
|
|
}
|
|
case Ci.nsIGeneralResponseData: {
|
|
componentName = "@mozilla.org/dom/payments/general-response-data;1";
|
|
break;
|
|
}
|
|
}
|
|
let component = this.componentsLoaded.get(componentName);
|
|
|
|
if (!component) {
|
|
component = Cc[componentName];
|
|
this.componentsLoaded.set(componentName, component);
|
|
}
|
|
|
|
return component.createInstance(componentInterface);
|
|
},
|
|
|
|
async fetchSavedAddresses() {
|
|
let savedAddresses = {};
|
|
for (let address of await formAutofillStorage.addresses.getAll()) {
|
|
savedAddresses[address.guid] = address;
|
|
}
|
|
return savedAddresses;
|
|
},
|
|
|
|
async fetchSavedPaymentCards() {
|
|
let savedBasicCards = {};
|
|
for (let card of await formAutofillStorage.creditCards.getAll()) {
|
|
savedBasicCards[card.guid] = card;
|
|
// Filter out the encrypted card number since the dialog content is
|
|
// considered untrusted and runs in a content process.
|
|
delete card["cc-number-encrypted"];
|
|
|
|
// ensure each card has a methodName property
|
|
if (!card.methodName) {
|
|
card.methodName = "basic-card";
|
|
}
|
|
}
|
|
return savedBasicCards;
|
|
},
|
|
|
|
fetchTempPaymentCards() {
|
|
let creditCards = this.temporaryStore.creditCards.getAll();
|
|
for (let card of Object.values(creditCards)) {
|
|
// Ensure each card has a methodName property.
|
|
if (!card.methodName) {
|
|
card.methodName = "basic-card";
|
|
}
|
|
}
|
|
return creditCards;
|
|
},
|
|
|
|
async onAutofillStorageChange() {
|
|
let [savedAddresses, savedBasicCards] = await Promise.all([
|
|
this.fetchSavedAddresses(),
|
|
this.fetchSavedPaymentCards(),
|
|
]);
|
|
|
|
this.sendMessageToContent("updateState", {
|
|
savedAddresses,
|
|
savedBasicCards,
|
|
});
|
|
},
|
|
|
|
sendMessageToContent(messageType, data = {}) {
|
|
this.mm.sendAsyncMessage("paymentChromeToContent", {
|
|
data,
|
|
messageType,
|
|
});
|
|
},
|
|
|
|
updateRequest() {
|
|
// There is no need to update this.request since the object is live
|
|
// and will automatically get updated if event.updateWith is used.
|
|
let requestSerialized = this._serializeRequest(this.request);
|
|
|
|
this.sendMessageToContent("updateState", {
|
|
request: requestSerialized,
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Recursively convert and filter input to the subset of data types supported by JSON
|
|
*
|
|
* @param {*} value - any type of input to serialize
|
|
* @param {string?} name - name or key associated with this input.
|
|
* E.g. property name or array index.
|
|
* @returns {*} serialized deep copy of the value
|
|
*/
|
|
_serializeRequest(value, name = null) {
|
|
// Primitives: String, Number, Boolean, null
|
|
let type = typeof value;
|
|
if (
|
|
value === null ||
|
|
type == "string" ||
|
|
type == "number" ||
|
|
type == "boolean"
|
|
) {
|
|
return value;
|
|
}
|
|
if (name == "topLevelPrincipal") {
|
|
// Manually serialize the nsIPrincipal.
|
|
let displayHost = value.URI.displayHost;
|
|
return {
|
|
URI: {
|
|
displayHost,
|
|
},
|
|
};
|
|
}
|
|
if (type == "function" || type == "undefined") {
|
|
return undefined;
|
|
}
|
|
// Structures: nsIArray
|
|
if (value instanceof Ci.nsIArray) {
|
|
let iface;
|
|
let items = [];
|
|
switch (name) {
|
|
case "displayItems": // falls through
|
|
case "additionalDisplayItems":
|
|
iface = Ci.nsIPaymentItem;
|
|
break;
|
|
case "shippingOptions":
|
|
iface = Ci.nsIPaymentShippingOption;
|
|
break;
|
|
case "paymentMethods":
|
|
iface = Ci.nsIPaymentMethodData;
|
|
break;
|
|
case "modifiers":
|
|
iface = Ci.nsIPaymentDetailsModifier;
|
|
break;
|
|
}
|
|
if (!iface) {
|
|
throw new Error(
|
|
`No interface associated with the members of the ${name} nsIArray`
|
|
);
|
|
}
|
|
for (let i = 0; i < value.length; i++) {
|
|
let item = value.queryElementAt(i, iface);
|
|
let result = this._serializeRequest(item, i);
|
|
if (result !== undefined) {
|
|
items.push(result);
|
|
}
|
|
}
|
|
return items;
|
|
}
|
|
// Structures: Arrays
|
|
if (Array.isArray(value)) {
|
|
let items = value
|
|
.map(item => this._serializeRequest(item))
|
|
.filter(item => item !== undefined);
|
|
return items;
|
|
}
|
|
// Structures: Objects
|
|
let obj = {};
|
|
for (let [key, item] of Object.entries(value)) {
|
|
let result = this._serializeRequest(item, key);
|
|
if (result !== undefined) {
|
|
obj[key] = result;
|
|
}
|
|
}
|
|
return obj;
|
|
},
|
|
|
|
async initializeFrame() {
|
|
// We don't do this earlier as it's only necessary once this function sends
|
|
// the initial saved records.
|
|
Services.obs.addObserver(this, "formautofill-storage-changed", true);
|
|
|
|
let requestSerialized = this._serializeRequest(this.request);
|
|
let chromeWindow = this.frameWeakRef.get().ownerGlobal;
|
|
let isPrivate = PrivateBrowsingUtils.isWindowPrivate(chromeWindow);
|
|
|
|
let [savedAddresses, savedBasicCards] = await Promise.all([
|
|
this.fetchSavedAddresses(),
|
|
this.fetchSavedPaymentCards(),
|
|
]);
|
|
|
|
this.sendMessageToContent("showPaymentRequest", {
|
|
request: requestSerialized,
|
|
savedAddresses,
|
|
tempAddresses: this.temporaryStore.addresses.getAll(),
|
|
savedBasicCards,
|
|
tempBasicCards: this.fetchTempPaymentCards(),
|
|
isPrivate,
|
|
});
|
|
},
|
|
|
|
debugFrame() {
|
|
// To avoid self-XSS-type attacks, ensure that Browser Chrome debugging is enabled.
|
|
if (!Services.prefs.getBoolPref("devtools.chrome.enabled", false)) {
|
|
Cu.reportError(
|
|
"devtools.chrome.enabled must be enabled to debug the frame"
|
|
);
|
|
return;
|
|
}
|
|
let { gDevToolsBrowser } = ChromeUtils.import(
|
|
"resource://devtools/client/framework/gDevTools.jsm"
|
|
);
|
|
gDevToolsBrowser.openContentProcessToolbox({
|
|
selectedBrowser: this.frameWeakRef.get(),
|
|
});
|
|
},
|
|
|
|
onOpenPreferences() {
|
|
BrowserWindowTracker.getTopWindow().openPreferences(
|
|
"privacy-form-autofill"
|
|
);
|
|
},
|
|
|
|
onPaymentCancel() {
|
|
const showResponse = this.createShowResponse({
|
|
acceptStatus: Ci.nsIPaymentActionResponse.PAYMENT_REJECTED,
|
|
});
|
|
|
|
paymentSrv.respondPayment(showResponse);
|
|
paymentUISrv.closePayment(this.request.requestId);
|
|
},
|
|
|
|
async onPay({
|
|
selectedPayerAddressGUID: payerGUID,
|
|
selectedPaymentCardGUID: paymentCardGUID,
|
|
selectedPaymentCardSecurityCode: cardSecurityCode,
|
|
selectedShippingAddressGUID: shippingGUID,
|
|
}) {
|
|
let methodData;
|
|
try {
|
|
methodData = await this._convertProfileBasicCardToPaymentMethodData(
|
|
paymentCardGUID,
|
|
cardSecurityCode
|
|
);
|
|
} catch (ex) {
|
|
// TODO (Bug 1498403): Some kind of "credit card storage error" here, perhaps asking user
|
|
// to re-enter credit card # from management UI.
|
|
Cu.reportError(ex);
|
|
return;
|
|
}
|
|
|
|
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 = "";
|
|
let payerEmail = "";
|
|
let payerPhone = "";
|
|
if (payerGUID) {
|
|
let payerData = await this._convertProfileAddressToPayerData(payerGUID);
|
|
payerName = payerData.payerName;
|
|
payerEmail = payerData.payerEmail;
|
|
payerPhone = payerData.payerPhone;
|
|
}
|
|
|
|
// Update the lastUsedTime for the payerAddress and paymentCard. Check if
|
|
// the record exists in formAutofillStorage because it may be temporary.
|
|
if (
|
|
shippingGUID &&
|
|
(await formAutofillStorage.addresses.get(shippingGUID))
|
|
) {
|
|
formAutofillStorage.addresses.notifyUsed(shippingGUID);
|
|
}
|
|
if (payerGUID && (await formAutofillStorage.addresses.get(payerGUID))) {
|
|
formAutofillStorage.addresses.notifyUsed(payerGUID);
|
|
}
|
|
if (await formAutofillStorage.creditCards.get(paymentCardGUID)) {
|
|
formAutofillStorage.creditCards.notifyUsed(paymentCardGUID);
|
|
}
|
|
|
|
this.pay({
|
|
methodName: "basic-card",
|
|
methodData,
|
|
payerName,
|
|
payerEmail,
|
|
payerPhone,
|
|
});
|
|
},
|
|
|
|
pay({ payerName, payerEmail, payerPhone, methodName, methodData }) {
|
|
const showResponse = this.createShowResponse({
|
|
acceptStatus: Ci.nsIPaymentActionResponse.PAYMENT_ACCEPTED,
|
|
payerName,
|
|
payerEmail,
|
|
payerPhone,
|
|
methodName,
|
|
methodData,
|
|
});
|
|
paymentSrv.respondPayment(showResponse);
|
|
this.sendMessageToContent("responseSent");
|
|
},
|
|
|
|
async onChangePayerAddress({ payerAddressGUID }) {
|
|
if (payerAddressGUID) {
|
|
// If a payer address was de-selected e.g. the selected address was deleted, we'll
|
|
// just wait to send the address change when the payer address is eventually selected
|
|
// before clicking Pay since it's a required field.
|
|
let {
|
|
payerName,
|
|
payerEmail,
|
|
payerPhone,
|
|
} = await this._convertProfileAddressToPayerData(payerAddressGUID);
|
|
paymentSrv.changePayerDetail(
|
|
this.request.requestId,
|
|
payerName,
|
|
payerEmail,
|
|
payerPhone
|
|
);
|
|
}
|
|
},
|
|
|
|
async onChangePaymentMethod({
|
|
selectedPaymentCardBillingAddressGUID: billingAddressGUID,
|
|
}) {
|
|
const methodName = "basic-card";
|
|
let methodDetails;
|
|
try {
|
|
let billingAddress = await this._convertProfileAddressToPaymentAddress(
|
|
billingAddressGUID
|
|
);
|
|
const basicCardChangeDetails = Cc[
|
|
"@mozilla.org/dom/payments/basiccard-change-details;1"
|
|
].createInstance(Ci.nsIBasicCardChangeDetails);
|
|
basicCardChangeDetails.initData(billingAddress);
|
|
methodDetails = basicCardChangeDetails.QueryInterface(
|
|
Ci.nsIMethodChangeDetails
|
|
);
|
|
} catch (ex) {
|
|
// TODO (Bug 1498403): Some kind of "credit card storage error" here, perhaps asking user
|
|
// to re-enter credit card # from management UI.
|
|
Cu.reportError(ex);
|
|
return;
|
|
}
|
|
|
|
paymentSrv.changePaymentMethod(
|
|
this.request.requestId,
|
|
methodName,
|
|
methodDetails
|
|
);
|
|
},
|
|
|
|
async onChangeShippingAddress({ shippingAddressGUID }) {
|
|
if (shippingAddressGUID) {
|
|
// If a shipping address was de-selected e.g. the selected address was deleted, we'll
|
|
// just wait to send the address change when the shipping address is eventually selected
|
|
// before clicking Pay since it's a required field.
|
|
let address = await this._convertProfileAddressToPaymentAddress(
|
|
shippingAddressGUID
|
|
);
|
|
paymentSrv.changeShippingAddress(this.request.requestId, address);
|
|
}
|
|
},
|
|
|
|
onChangeShippingOption({ optionID }) {
|
|
// Note, failing here on browser_host_name.js because the test closes
|
|
// the dialog before the onChangeShippingOption is called, thus
|
|
// deleting the request and making the requestId invalid. Unclear
|
|
// why we aren't seeing the same issue with onChangeShippingAddress.
|
|
paymentSrv.changeShippingOption(this.request.requestId, optionID);
|
|
},
|
|
|
|
onCloseDialogMessage() {
|
|
// The PR is complete(), just close the dialog
|
|
paymentUISrv.closePayment(this.request.requestId);
|
|
},
|
|
|
|
async onUpdateAutofillRecord(collectionName, record, guid, messageID) {
|
|
let responseMessage = {
|
|
guid,
|
|
messageID,
|
|
stateChange: {},
|
|
};
|
|
try {
|
|
let isTemporary = record.isTemporary;
|
|
let collection = isTemporary
|
|
? this.temporaryStore[collectionName]
|
|
: formAutofillStorage[collectionName];
|
|
|
|
if (guid) {
|
|
// We want to preserve old properties since the edit forms are often
|
|
// shown without all fields visible/enabled and we don't want those
|
|
// fields to be blanked upon saving. Examples of hidden/disabled fields:
|
|
// email, cc-number, mailing-address on the payer forms, and payer fields
|
|
// not requested in the payer form.
|
|
let preserveOldProperties = true;
|
|
await collection.update(guid, record, preserveOldProperties);
|
|
} else {
|
|
responseMessage.guid = await collection.add(record);
|
|
}
|
|
|
|
if (isTemporary && collectionName == "addresses") {
|
|
// there will be no formautofill-storage-changed event to update state
|
|
// so add updated collection here
|
|
Object.assign(responseMessage.stateChange, {
|
|
tempAddresses: this.temporaryStore.addresses.getAll(),
|
|
});
|
|
}
|
|
if (isTemporary && collectionName == "creditCards") {
|
|
// there will be no formautofill-storage-changed event to update state
|
|
// so add updated collection here
|
|
Object.assign(responseMessage.stateChange, {
|
|
tempBasicCards: this.fetchTempPaymentCards(),
|
|
});
|
|
}
|
|
} catch (ex) {
|
|
responseMessage.error = true;
|
|
Cu.reportError(ex);
|
|
} finally {
|
|
this.sendMessageToContent(
|
|
"updateAutofillRecord:Response",
|
|
responseMessage
|
|
);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @implements {nsIObserver}
|
|
* @param {nsISupports} subject
|
|
* @param {string} topic
|
|
* @param {string} data
|
|
*/
|
|
observe(subject, topic, data) {
|
|
switch (topic) {
|
|
case "formautofill-storage-changed": {
|
|
if (data == "notifyUsed") {
|
|
break;
|
|
}
|
|
this.onAutofillStorageChange();
|
|
break;
|
|
}
|
|
case "message-manager-close": {
|
|
if (this.mm && subject == this.mm) {
|
|
// Remove the observer to avoid message manager errors while the dialog
|
|
// is closing and tests are cleaning up autofill storage.
|
|
Services.obs.removeObserver(this, "formautofill-storage-changed");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
receiveMessage({ data }) {
|
|
let { messageType } = data;
|
|
|
|
switch (messageType) {
|
|
case "debugFrame": {
|
|
this.debugFrame();
|
|
break;
|
|
}
|
|
case "initializeRequest": {
|
|
this.initializeFrame();
|
|
break;
|
|
}
|
|
case "changePayerAddress": {
|
|
this.onChangePayerAddress(data);
|
|
break;
|
|
}
|
|
case "changePaymentMethod": {
|
|
this.onChangePaymentMethod(data);
|
|
break;
|
|
}
|
|
case "changeShippingAddress": {
|
|
this.onChangeShippingAddress(data);
|
|
break;
|
|
}
|
|
case "changeShippingOption": {
|
|
this.onChangeShippingOption(data);
|
|
break;
|
|
}
|
|
case "closeDialog": {
|
|
this.onCloseDialogMessage();
|
|
break;
|
|
}
|
|
case "openPreferences": {
|
|
this.onOpenPreferences();
|
|
break;
|
|
}
|
|
case "paymentCancel": {
|
|
this.onPaymentCancel();
|
|
break;
|
|
}
|
|
case "paymentDialogReady": {
|
|
this.frameWeakRef.get().dispatchEvent(
|
|
new Event("tabmodaldialogready", {
|
|
bubbles: true,
|
|
})
|
|
);
|
|
break;
|
|
}
|
|
case "pay": {
|
|
this.onPay(data);
|
|
break;
|
|
}
|
|
case "updateAutofillRecord": {
|
|
this.onUpdateAutofillRecord(
|
|
data.collectionName,
|
|
data.record,
|
|
data.guid,
|
|
data.messageID
|
|
);
|
|
break;
|
|
}
|
|
default: {
|
|
throw new Error(
|
|
`paymentDialogWrapper: Unexpected messageType: ${messageType}`
|
|
);
|
|
}
|
|
}
|
|
},
|
|
};
|