Bug 1423053 - Support accepting a payment request from the UI (with dummy data). r=jaws

MozReview-Commit-ID: 8OZzdvy1as

--HG--
extra : rebase_source : da0149737028b9ef93d2254ca317888e7fccdc96
This commit is contained in:
Matthew Noorenberghe 2017-12-20 16:58:56 -05:00
Родитель 05193eefff
Коммит 76d7d4f206
9 изменённых файлов: 321 добавлений и 16 удалений

Просмотреть файл

@ -15,7 +15,7 @@ const paymentSrv = Cc["@mozilla.org/dom/payments/payment-request-service;1"]
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
let PaymentDialog = {
var PaymentDialog = {
componentsLoaded: new Map(),
frame: null,
mm: null,
@ -38,10 +38,15 @@ let PaymentDialog = {
this.frame.src = "resource://payments/paymentRequest.xhtml";
},
createShowResponse({acceptStatus, methodName = "", data = null,
payerName = "", payerEmail = "", payerPhone = ""}) {
createShowResponse({
acceptStatus,
methodName = "",
methodData = null,
payerName = "",
payerEmail = "",
payerPhone = "",
}) {
let showResponse = this.createComponentInstance(Ci.nsIPaymentShowActionResponse);
let methodData = this.createComponentInstance(Ci.nsIGeneralResponseData);
showResponse.init(this.request.requestId,
acceptStatus,
@ -53,6 +58,60 @@ let PaymentDialog = {
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({
country = "",
addressLines = [],
region = "",
city = "",
dependentLocality = "",
postalCode = "",
sortingCode = "",
languageCode = "",
organization = "",
recipient = "",
phone = "",
}) {
const billingAddress = 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);
}
billingAddress.init(country,
addressLine,
region,
city,
dependentLocality,
postalCode,
sortingCode,
languageCode,
organization,
recipient,
phone);
return billingAddress;
},
createComponentInstance(componentInterface) {
let componentName;
switch (componentInterface) {
@ -83,6 +142,25 @@ let PaymentDialog = {
window.close();
},
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,
});
paymentSrv.respondPayment(showResponse);
},
receiveMessage({data}) {
let {messageType} = data;
@ -110,10 +188,17 @@ let PaymentDialog = {
this.onPaymentCancel();
break;
}
case "pay": {
this.pay(data);
break;
}
}
},
};
let frame = document.getElementById("paymentRequestFrame");
let requestId = (new URLSearchParams(window.location.search)).get("requestId");
PaymentDialog.init(requestId, frame);
if ("document" in this) {
// Running in a browser, not a unit test
let frame = document.getElementById("paymentRequestFrame");
let requestId = (new URLSearchParams(window.location.search)).get("requestId");
PaymentDialog.init(requestId, frame);
}

Просмотреть файл

@ -23,6 +23,9 @@ class PaymentDialog extends PaymentStateSubscriberMixin(HTMLElement) {
this._cancelButton = contents.querySelector("#cancel");
this._cancelButton.addEventListener("click", this.cancelRequest);
this._payButton = contents.querySelector("#pay");
this._payButton.addEventListener("click", this.pay);
this.appendChild(contents);
super.connectedCallback();
@ -30,6 +33,7 @@ class PaymentDialog extends PaymentStateSubscriberMixin(HTMLElement) {
disconnectedCallback() {
this._cancelButtonEl.removeEventListener("click", this.cancelRequest);
this._cancelButtonEl.removeEventListener("click", this.pay);
super.disconnectedCallback();
}
@ -37,6 +41,19 @@ class PaymentDialog extends PaymentStateSubscriberMixin(HTMLElement) {
PaymentRequest.cancel();
}
pay() {
PaymentRequest.pay({
methodName: "basic-card",
methodData: {
cardholderName: "John Doe",
cardNumber: "9999999999",
expiryMonth: "01",
expiryYear: "9999",
cardSecurityCode: "999",
},
});
}
setLoadingState(state) {
this.requestStore.setState(state);
}

Просмотреть файл

@ -20,9 +20,3 @@ html {
font-size: 15px;
font-weight: bold;
}
#cancel {
position: absolute;
bottom: 10px;
left: 10px;
}

Просмотреть файл

@ -96,6 +96,10 @@ let PaymentRequest = {
this.sendMessageToChrome("paymentCancel");
},
pay(data) {
this.sendMessageToChrome("pay", data);
},
onPaymentRequestUnload() {
// remove listeners that may be used multiple times here
window.removeEventListener("paymentChromeToContent", this);

Просмотреть файл

@ -29,7 +29,8 @@
<currency-amount></currency-amount>
</div>
<div id="controls-container">
<button id="cancel">Cancel payment</button>
<button id="cancel">Cancel</button>
<button id="pay">Pay</button>
</div>
</template>
</head>

Просмотреть файл

@ -10,6 +10,20 @@ this.PaymentTestUtils = {
*/
ContentTasks: {
/* eslint-env mozilla/frame-script */
/**
* Add a completion handler to the existing `showPromise` to call .complete().
* @returns {Object} representing the PaymentResponse
*/
addCompletionHandler: async () => {
let response = await content.showPromise;
response.complete();
return {
response: response.toJSON(),
// XXX: Bug NNN: workaround for `details` not being included in `toJSON`.
methodDetails: response.details,
};
},
/**
* Create a new payment request and cache it as `rq`.
*
@ -34,9 +48,11 @@ this.PaymentTestUtils = {
createAndShowRequest: ({methodData, details, options}) => {
const rq = new content.PaymentRequest(methodData, details, options);
content.rq = rq; // assign it so we can retrieve it later
rq.show();
content.showPromise = rq.show();
},
},
DialogContentTasks: {
/**
* Click the cancel button
*
@ -48,6 +64,14 @@ this.PaymentTestUtils = {
manuallyClickCancel: () => {
content.document.getElementById("cancel").click();
},
/**
* Do the minimum possible to complete the payment succesfully.
* @returns {undefined}
*/
completePayment: () => {
content.document.getElementById("pay").click();
},
},
/**

Просмотреть файл

@ -44,7 +44,45 @@ add_task(async function test_show_manualAbort_dialog() {
ok(frame, "Got payment frame");
await dialogReadyPromise;
info("dialog ready");
spawnPaymentDialogTask(frame, PTU.ContentTasks.manuallyClickCancel);
spawnPaymentDialogTask(frame, PTU.DialogContentTasks.manuallyClickCancel);
await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
});
});
add_task(async function test_show_completePayment() {
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);
// get a reference to the UI dialog and the requestId
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");
await dialogReadyPromise;
info("dialog ready, 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 methodDetails = result.methodDetails;
is(methodDetails.cardholderName, "John Doe", "Check cardholderName");
is(methodDetails.cardNumber, "9999999999", "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");
});
});

Просмотреть файл

@ -0,0 +1,141 @@
"use strict";
/**
* Basic checks to ensure that helpers constructing responses map their
* destructured arguments properly to the `init` methods. Full testing of the init
* methods is left to the DOM code.
*/
/* import-globals-from ../../content/PaymentDialog.js */
let dialogGlobal = {};
Services.scriptloader.loadSubScript("chrome://payments/content/paymentDialog.js", dialogGlobal);
/**
* @param {Object} responseData with properties in the order matching `nsIBasicCardResponseData`
* init method args.
* @returns {string} serialized card data
*/
function serializeBasicCardResponseData(responseData) {
return [...Object.entries(responseData)].map(array => array.join(":")).join(";") + ";";
}
add_task(async function test_createBasicCardResponseData_basic() {
let expected = {
cardholderName: "John Smith",
cardNumber: "1234567890",
expiryMonth: "01",
expiryYear: "2017",
cardSecurityCode: "0123",
};
let actual = dialogGlobal.PaymentDialog.createBasicCardResponseData(expected);
let expectedSerialized = serializeBasicCardResponseData(expected);
do_check_eq(actual.data, expectedSerialized, "Check data");
});
add_task(async function test_createBasicCardResponseData_minimal() {
let expected = {
cardNumber: "1234567890",
};
let actual = dialogGlobal.PaymentDialog.createBasicCardResponseData(expected);
let expectedSerialized = serializeBasicCardResponseData(expected);
do_print(actual.data);
do_check_eq(actual.data, expectedSerialized, "Check data");
});
add_task(async function test_createBasicCardResponseData_withoutNumber() {
let data = {
cardholderName: "John Smith",
expiryMonth: "01",
expiryYear: "2017",
cardSecurityCode: "0123",
};
Assert.throws(() => dialogGlobal.PaymentDialog.createBasicCardResponseData(data),
/NS_ERROR_FAILURE/,
"Check cardNumber is required");
});
function checkAddress(actual, expected) {
for (let [propName, propVal] of Object.entries(expected)) {
if (propName == "addressLines") {
// Note the singular vs. plural here.
do_check_eq(actual.addressLine.length, propVal.length, "Check number of address lines");
for (let [i, line] of expected.addressLines.entries()) {
do_check_eq(actual.addressLine.queryElementAt(i, Ci.nsISupportsString).data, line,
`Check ${propName} line ${i}`);
}
continue;
}
do_check_eq(actual[propName], propVal, `Check ${propName}`);
}
}
add_task(async function test_createPaymentAddress_minimal() {
let data = {
country: "CA",
};
let actual = dialogGlobal.PaymentDialog.createPaymentAddress(data);
checkAddress(actual, data);
});
add_task(async function test_createPaymentAddress_basic() {
let data = {
country: "CA",
addressLines: [
"123 Sesame Street",
"P.O. Box ABC",
],
region: "ON",
city: "Delhi",
dependentLocality: "N/A",
postalCode: "94041",
sortingCode: "1234",
languageCode: "en-CA",
organization: "Mozilla Corporation",
recipient: "John Smith",
phone: "+15195555555",
};
let actual = dialogGlobal.PaymentDialog.createPaymentAddress(data);
checkAddress(actual, data);
});
add_task(async function test_createShowResponse_basic() {
let requestId = "876hmbvfd45hb";
dialogGlobal.PaymentDialog.request = {
requestId,
};
let cardData = {
cardholderName: "John Smith",
cardNumber: "1234567890",
expiryMonth: "01",
expiryYear: "2099",
cardSecurityCode: "0123",
};
let methodData = dialogGlobal.PaymentDialog.createBasicCardResponseData(cardData);
let responseData = {
acceptStatus: Ci.nsIPaymentActionResponse.PAYMENT_ACCEPTED,
methodName: "basic-card",
methodData,
payerName: "My Name",
payerEmail: "my.email@example.com",
payerPhone: "+15195555555",
};
let actual = dialogGlobal.PaymentDialog.createShowResponse(responseData);
for (let [propName, propVal] of Object.entries(actual)) {
if (typeof(propVal) != "string") {
continue;
}
if (propName == "requestId") {
do_check_eq(propVal, requestId, `Check ${propName}`);
continue;
}
if (propName == "data") {
do_check_eq(propVal, serializeBasicCardResponseData(cardData), `Check ${propName}`);
continue;
}
do_check_eq(propVal, responseData[propName], `Check ${propName}`);
}
});

Просмотреть файл

@ -2,3 +2,4 @@
head = head.js
[test_PaymentsStore.js]
[test_response_creation.js]