зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
05193eefff
Коммит
76d7d4f206
|
@ -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]
|
||||
|
|
Загрузка…
Ссылка в новой задаче