Bug 1429195 - Send the selected payment card to the wrapper and DOM. r=jaws
MozReview-Commit-ID: 8SqXrnvenGB
--- a/toolkit/components/payments/content/paymentDialogWrapper.js
+++ b/toolkit/components/payments/content/paymentDialogWrapper.js
@@ -11,16 +11,19 @@
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
const paymentSrv = Cc["@mozilla.org/dom/payments/payment-request-service;1"]
.getService(Ci.nsIPaymentRequestService);
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+ChromeUtils.defineModuleGetter(this, "MasterPassword",
+ "resource://formautofill/MasterPassword.jsm");
+
XPCOMUtils.defineLazyGetter(this, "profileStorage", () => {
let profileStorage;
try {
profileStorage = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {})
.profileStorage;
profileStorage.initialize();
} catch (ex) {
profileStorage = null;
@@ -36,17 +39,23 @@ var paymentDialogWrapper = {
mm: null,
request: null,
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIObserver,
Ci.nsISupportsWeakReference,
]),
- _convertProfileAddressToPaymentAddress(guid) {
+ /**
+ * 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}`);
}
let address = this.createPaymentAddress({
country: addressData.country,
addressLines: addressData["street-address"].split("\n"),
@@ -56,16 +65,51 @@ var paymentDialogWrapper = {
organization: addressData.organization,
recipient: addressData.name,
phone: addressData.tel,
});
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 the user cancels entering their master password or an error decrypting
+ * @returns {nsIBasicCardResponseData?} returns response data or null (if the
+ * master password dialog was cancelled);
+ */
+ async _convertProfileBasicCardToPaymentMethodData(guid, cardSecurityCode) {
+ let cardData = profileStorage.creditCards.get(guid);
+ if (!cardData) {
+ throw new Error(`Basic card not found in storage: ${guid}`);
+ }
+
+ let cardNumber;
+ try {
+ cardNumber = await MasterPassword.decrypt(cardData["cc-number-encrypted"], true);
+ } catch (ex) {
+ if (ex.result != Cr.NS_ERROR_ABORT) {
+ throw ex;
+ }
+ // User canceled master password entry
+ return null;
+ }
+
+ 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,
+ });
+
+ return methodData;
+ },
+
init(requestId, frame) {
if (!requestId || typeof(requestId) != "string") {
throw new Error("Invalid PaymentRequest ID");
}
this.request = paymentSrv.getPaymentRequestById(requestId);
if (!this.request) {
throw new Error(`PaymentRequest not found: ${requestId}`);
@@ -244,37 +288,56 @@ var paymentDialogWrapper = {
onPaymentCancel() {
const showResponse = this.createShowResponse({
acceptStatus: Ci.nsIPaymentActionResponse.PAYMENT_REJECTED,
});
paymentSrv.respondPayment(showResponse);
window.close();
},
+ async onPay({
+ 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;
+ }
+
+ this.pay({
+ methodName: "basic-card",
+ methodData,
+ });
+ },
+
pay({
payerName,
payerEmail,
payerPhone,
methodName,
methodData,
}) {
- let basicCardData = this.createBasicCardResponseData(methodData);
const showResponse = this.createShowResponse({
acceptStatus: Ci.nsIPaymentActionResponse.PAYMENT_ACCEPTED,
payerName,
payerEmail,
payerPhone,
methodName,
- methodData: basicCardData,
+ methodData,
});
paymentSrv.respondPayment(showResponse);
},
- onChangeShippingAddress({shippingAddressGUID}) {
- let address = this._convertProfileAddressToPaymentAddress(shippingAddressGUID);
+ async onChangeShippingAddress({shippingAddressGUID}) {
+ let address = await this._convertProfileAddressToPaymentAddress(shippingAddressGUID);
paymentSrv.changeShippingAddress(this.request.requestId, address);
},
/**
* @implements {nsIObserver}
* @param {nsISupports} subject
* @param {string} topic
* @param {string} data
@@ -307,17 +370,17 @@ var paymentDialogWrapper = {
this.onChangeShippingAddress(data);
break;
}
case "paymentCancel": {
this.onPaymentCancel();
break;
}
case "pay": {
- this.pay(data);
+ this.onPay(data);
break;
}
}
},
};
if ("document" in this) {
// Running in a browser, not a unit test
--- a/toolkit/components/payments/res/containers/payment-dialog.js
+++ b/toolkit/components/payments/res/containers/payment-dialog.js
@@ -58,25 +58,24 @@ class PaymentDialog extends PaymentState
}
}
cancelRequest() {
paymentRequest.cancel();
}
pay() {
+ let {
+ selectedPaymentCard,
+ selectedPaymentCardSecurityCode,
+ } = this.requestStore.getState();
+
paymentRequest.pay({
- methodName: "basic-card",
- methodData: {
- cardholderName: "John Doe",
- cardNumber: "9999999999",
- expiryMonth: "01",
- expiryYear: "9999",
- cardSecurityCode: "999",
- },
+ selectedPaymentCardGUID: selectedPaymentCard,
+ selectedPaymentCardSecurityCode,
});
}
changeShippingAddress(shippingAddressGUID) {
paymentRequest.changeShippingAddress({
shippingAddressGUID,
});
}
@@ -107,16 +106,17 @@ class PaymentDialog extends PaymentState
});
}
// Ensure `selectedPaymentCard` never refers to a deleted payment card and refers
// to a payment card if one exists.
if (!savedBasicCards[selectedPaymentCard]) {
this.requestStore.setState({
selectedPaymentCard: Object.keys(savedBasicCards)[0] || null,
+ selectedPaymentCardSecurityCode: null,
});
}
}
stateChangeCallback(state) {
super.stateChangeCallback(state);
if (state.selectedShippingAddress != this._cachedState.selectedShippingAddress) {
--- a/toolkit/components/payments/test/PaymentTestUtils.jsm
+++ b/toolkit/components/payments/test/PaymentTestUtils.jsm
@@ -67,16 +67,24 @@ this.PaymentTestUtils = {
/**
* Do the minimum possible to complete the payment succesfully.
* @returns {undefined}
*/
completePayment: () => {
content.document.getElementById("pay").click();
},
+
+ setSecurityCode: ({securityCode}) => {
+ // Waive the xray to access the untrusted `securityCodeInput` property
+ let picker = Cu.waiveXrays(content.document.querySelector("payment-method-picker"));
+ // Unwaive to access the ChromeOnly `setUserInput` API.
+ // setUserInput dispatches changes events.
+ Cu.unwaiveXrays(picker.securityCodeInput).setUserInput(securityCode);
+ },
},
/**
* Common PaymentMethodData for testing
*/
MethodData: {
basicCard: {
supportedMethods: "basic-card",
--- a/toolkit/components/payments/test/browser/browser_show_dialog.js
+++ b/toolkit/components/payments/test/browser/browser_show_dialog.js
@@ -61,16 +61,28 @@ add_task(async function test_show_comple
"postal-code": "02139",
country: "US",
tel: "+16172535702",
email: "timbl@example.org",
};
profileStorage.addresses.add(address);
await onChanged;
+ onChanged = TestUtils.topicObserved("formautofill-storage-changed",
+ (subject, data) => data == "add");
+ let card = {
+ "cc-exp-month": 1,
+ "cc-exp-year": 9999,
+ "cc-name": "John Doe",
+ "cc-number": "999999999999",
+ };
+
+ profileStorage.creditCards.add(card);
+ await onChanged;
+
await BrowserTestUtils.withNewTab({
gBrowser,
url: BLANK_PAGE_URL,
}, async browser => {
let dialogReadyPromise = waitForWidgetReady();
// start by creating a PaymentRequest, and show it
await ContentTask.spawn(browser, {methodData, details}, PTU.ContentTasks.createAndShowRequest);
@@ -78,40 +90,44 @@ add_task(async function test_show_comple
let [win] = await Promise.all([getPaymentWidget(), dialogReadyPromise]);
ok(win, "Got payment widget");
let requestId = paymentUISrv.requestIdForWindow(win);
ok(requestId, "requestId should be defined");
is(win.closed, false, "dialog should not be closed");
let frame = await getPaymentFrame(win);
ok(frame, "Got payment frame");
+ info("entering CSC");
+ await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.setSecurityCode, {
+ securityCode: "999",
+ });
info("clicking pay");
spawnPaymentDialogTask(frame, PTU.DialogContentTasks.completePayment);
// Add a handler to complete the payment above.
info("acknowledging the completion from the merchant page");
let result = await ContentTask.spawn(browser, {}, PTU.ContentTasks.addCompletionHandler);
- is(result.response.methodName, "basic-card", "Check methodName");
let addressLines = address["street-address"].split("\n");
let actualShippingAddress = result.response.shippingAddress;
is(actualShippingAddress.addressLine[0], addressLines[0], "Address line 1 should match");
is(actualShippingAddress.addressLine[1], addressLines[1], "Address line 2 should match");
is(actualShippingAddress.country, address.country, "Country should match");
is(actualShippingAddress.region, address["address-level1"], "Region should match");
is(actualShippingAddress.city, address["address-level2"], "City should match");
is(actualShippingAddress.postalCode, address["postal-code"], "Zip code should match");
is(actualShippingAddress.organization, address.organization, "Org should match");
is(actualShippingAddress.recipient,
`${address["given-name"]} ${address["additional-name"]} ${address["family-name"]}`,
"Recipient country should match");
is(actualShippingAddress.phone, address.tel, "Phone should match");
+ is(result.response.methodName, "basic-card", "Check methodName");
let methodDetails = result.methodDetails;
is(methodDetails.cardholderName, "John Doe", "Check cardholderName");
- is(methodDetails.cardNumber, "9999999999", "Check cardNumber");
+ is(methodDetails.cardNumber, "999999999999", "Check cardNumber");
is(methodDetails.expiryMonth, "01", "Check expiryMonth");
is(methodDetails.expiryYear, "9999", "Check expiryYear");
is(methodDetails.cardSecurityCode, "999", "Check cardSecurityCode");
await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
});
});