Bug 767818 - Implement navigator.pay. Part 3 - DOM implementation; r=fabrice

This commit is contained in:
Fernando Jiménez 2012-08-29 18:41:34 -03:00
parent 2368411d52
commit 0e9c43b39f
7 changed files with 677 additions and 0 deletions

26
dom/payment/Makefile.in Normal file
View File

@ -0,0 +1,26 @@
# 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/.
DEPTH = @DEPTH@
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
PARALLEL_DIRS = interfaces
EXTRA_COMPONENTS = \
Payment.js \
PaymentFlowInfo.js \
PaymentRequestInfo.js \
Payment.manifest \
$(NULL)
EXTRA_JS_MODULES += \
Payment.jsm \
$(NULL)
include $(topsrcdir)/config/config.mk
include $(topsrcdir)/config/rules.mk

88
dom/payment/Payment.js Normal file
View File

@ -0,0 +1,88 @@
/* 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/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
const PAYMENTCONTENTHELPER_CID =
Components.ID("{a920adc0-c36e-4fd0-8de0-aac1ac6ebbd0}");
const PAYMENT_IPC_MSG_NAMES = ["Payment:Success",
"Payment:Failed"];
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1",
"nsIMessageSender");
function debug (s) {
//dump("-*- PaymentContentHelper: " + s + "\n");
};
function PaymentContentHelper() {
};
PaymentContentHelper.prototype = {
__proto__: DOMRequestIpcHelper.prototype,
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMNavigatorPayment,
Ci.nsIDOMGlobalPropertyInitializer]),
classID: PAYMENTCONTENTHELPER_CID,
classInfo: XPCOMUtils.generateCI({
classID: PAYMENTCONTENTHELPER_CID,
contractID: "@mozilla.org/payment/content-helper;1",
classDescription: "Payment Content Helper",
flags: Ci.nsIClassInfo.DOM_OBJECT,
interfaces: [Ci.nsIDOMNavigatorPayment]
}),
// nsIDOMNavigatorPayment
pay: function pay(aJwts) {
let request = this.createRequest();
let requestId = this.getRequestId(request);
if (!Array.isArray(aJwts)) {
aJwts = [aJwts];
}
cpmm.sendAsyncMessage("Payment:Pay", {
jwts: aJwts,
requestId: requestId
});
return request;
},
// nsIDOMGlobalPropertyInitializer
init: function(aWindow) {
this.initHelper(aWindow, PAYMENT_IPC_MSG_NAMES);
return this.pay.bind(this);
},
// nsIFrameMessageListener
receiveMessage: function receiveMessage(aMessage) {
let name = aMessage.name;
let msg = aMessage.json;
debug("Received message '" + name + "': " + JSON.stringify(msg));
let requestId = msg.requestId;
let request = this.takeRequest(requestId);
if (!request) {
return;
}
switch (name) {
case "Payment:Success":
Services.DOMRequest.fireSuccess(request, msg.result);
break;
case "Payment:Failed":
Services.DOMRequest.fireError(request, msg.errorMsg);
break;
}
}
};
const NSGetFactory = XPCOMUtils.generateNSGetFactory([PaymentContentHelper]);

370
dom/payment/Payment.jsm Normal file
View File

@ -0,0 +1,370 @@
/* 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/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
let EXPORTED_SYMBOLS = [];
const PAYMENT_IPC_MSG_NAMES = ["Payment:Pay",
"Payment:Success",
"Payment:Failed"];
const PREF_PAYMENTPROVIDERS_BRANCH = "dom.payment.provider.";
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
"@mozilla.org/parentprocessmessagemanager;1",
"nsIMessageListenerManager");
XPCOMUtils.defineLazyServiceGetter(this, "prefService",
"@mozilla.org/preferences-service;1",
"nsIPrefService");
function debug (s) {
//dump("-*- PaymentManager: " + s + "\n");
};
let PaymentManager = {
init: function init() {
this.requestId = null;
// Payment providers data are stored as a preference.
this.registeredProviders = null;
this.messageManagers = {};
for each (let msgname in PAYMENT_IPC_MSG_NAMES) {
ppmm.addMessageListener(msgname, this);
}
Services.obs.addObserver(this, "xpcom-shutdown", false);
},
/**
* Process a message from the content process.
*/
receiveMessage: function receiveMessage(aMessage) {
let name = aMessage.name;
let msg = aMessage.json;
debug("Received '" + name + "' message from content process");
if (msg.requestId) {
this.requestId = msg.requestId;
}
switch (name) {
case "Payment:Pay": {
// First of all, we register the payment providers.
if (!this.registeredProviders) {
this.registeredProviders = {};
this.registerPaymentProviders();
}
// We save the message target message manager so we can later dispatch
// back messages without broadcasting to all child processes.
this.messageManagers[this.requestId] = aMessage.target;
// We check the jwt type and look for a match within the
// registered payment providers to get the correct payment request
// information.
let paymentRequests = [];
let jwtTypes = [];
for (let i in msg.jwts) {
let pr = this.getPaymentRequestInfo(msg.jwts[i]);
if (!pr) {
continue;
}
// We consider jwt type repetition an error.
if (jwtTypes[pr.type]) {
this.paymentFailed("DUPLICATE_JWT_TYPE");
return;
}
jwtTypes[pr.type] = true;
paymentRequests.push(pr);
}
if (!paymentRequests.length) {
this.paymentFailed("NO_VALID_PAYMENT_REQUEST");
return;
}
// After getting the list of valid payment requests, we ask the user
// for confirmation before sending any request to any payment provider.
// If there is more than one choice, we also let the user select the one
// that he prefers.
let glue = Cc["@mozilla.org/payment/ui-glue;1"]
.createInstance(Ci.nsIPaymentUIGlue);
if (!glue) {
debug("Could not create nsIPaymentUIGlue instance");
this.paymentFailed("CREATE_PAYMENT_GLUE_FAILED");
return;
}
let confirmPaymentSuccessCb = function successCb(aResult) {
// Get the appropriate payment provider data based on user's choice.
let selectedProvider = this.registeredProviders[aResult];
if (!selectedProvider || !selectedProvider.uri) {
debug("Could not retrieve a valid provider based on user's " +
"selection");
this.paymentFailed("NO_VALID_SELECTED_PROVIDER");
return;
}
let jwt;
for (let i in paymentRequests) {
if (paymentRequests[i].type == aResult) {
jwt = paymentRequests[i].jwt;
break;
}
}
if (!jwt) {
debug("The selected request has no JWT information associated");
this.paymentFailed("NO_JWT_ASSOCIATED_TO_REQUEST");
return;
}
this.showPaymentFlow(selectedProvider, jwt);
};
let confirmPaymentErrorCb = this.paymentFailed;
glue.confirmPaymentRequest(paymentRequests,
confirmPaymentSuccessCb.bind(this),
confirmPaymentErrorCb.bind(this));
break;
}
case "Payment:Success":
case "Payment:Failed": {
let mm = this.messageManagers[this.requestId];
mm.sendAsyncMessage(name, {
requestId: this.requestId,
result: msg.result,
errorMsg: msg.errorMsg
});
break;
}
}
},
/**
* Helper function to register payment providers stored as preferences.
*/
registerPaymentProviders: function registerPaymentProviders() {
let paymentProviders = prefService
.getBranch(PREF_PAYMENTPROVIDERS_BRANCH)
.getChildList("");
// First get the numbers of the providers by getting all ###.uri prefs.
let nums = [];
for (let i in paymentProviders) {
let match = /^(\d+)\.uri$/.exec(paymentProviders[i]);
if (!match) {
continue;
} else {
nums.push(match[1]);
}
}
// Now register the payment providers.
for (let i in nums) {
let branch = prefService
.getBranch(PREF_PAYMENTPROVIDERS_BRANCH + nums[i] + ".");
let vals = branch.getChildList("");
if (vals.length == 0) {
return;
}
try {
let type = branch.getCharPref("type");
if (type in this.registeredProviders) {
continue;
}
this.registeredProviders[type] = {
name: branch.getCharPref("name"),
uri: branch.getCharPref("uri"),
description: branch.getCharPref("description"),
requestMethod: branch.getCharPref("requestMethod")
};
debug("Registered Payment Providers: " +
JSON.stringify(this.registeredProviders[type]));
} catch (ex) {
debug("An error ocurred registering a payment provider. " + ex);
}
}
},
/**
* Helper for sending a Payment:Failed message to the parent process.
*/
paymentFailed: function paymentFailed(aErrorMsg) {
let mm = this.messageManagers[this.requestId];
mm.sendAsyncMessage("Payment:Failed", {
requestId: this.requestId,
errorMsg: aErrorMsg
});
},
/**
* Helper function to get the payment request info according to the jwt
* type. Payment provider's data is stored as a preference.
*/
getPaymentRequestInfo: function getPaymentRequestInfo(aJwt) {
if (!aJwt) {
return null;
}
// First thing, we check that the jwt type is an allowed type and has a
// payment provider flow information associated.
// A jwt string consists in three parts separated by period ('.'): header,
// payload and signature.
let segments = aJwt.split('.');
if (segments.length !== 3) {
debug("Error getting payment provider's uri. " +
"Not enough or too many segments");
return null;
}
let payloadObject;
try {
// We only care about the payload segment, which contains the jwt type
// that should match with any of the stored payment provider's data and
// the payment request information to be shown to the user.
let payload = atob(segments[1]);
debug("Payload " + payload);
// We get rid off the quotes and backslashes so we can parse the JSON
// object.
if (payload.charAt(0) === '"') {
payload = payload.substr(1);
}
if (payload.charAt(payload.length - 1) === '"') {
payload = payload.slice(0, -1);
}
payload = payload.replace(/\\/g, '');
payloadObject = JSON.parse(payload);
} catch (e) {
debug("Error decoding jwt " + e);
return null;
}
if (!payloadObject || !payloadObject.typ || !payloadObject.request) {
debug("Error decoding jwt. Not valid jwt. " +
"No payload or jwt type or request found");
return null;
}
// Once we got the jwt 'typ' value we look for a match within the payment
// providers stored preferences.
let provider = this.registeredProviders[payloadObject.typ];
if (!provider || !provider.uri || !provider.name) {
debug("Not registered payment provider for jwt type: " +
payloadObject.typ);
return null;
}
// We only allow https for payment providers uris.
if (!/^https/.exec(provider.uri.toLowerCase())) {
debug("Payment provider uris must be https: " + provider.uri);
return null;
}
let pldRequest = payloadObject.request;
let request;
if (pldRequest.refund) {
// The request is a refund request.
request = Cc["@mozilla.org/payment/request-refund-info;1"]
.createInstance(Ci.nsIDOMPaymentRequestRefundInfo);
// Refund request should contain a refund reason.
if (!request || !pldRequest.reason) {
debug("Not valid refund request");
return null;
}
request.wrappedJSObject.init(aJwt,
payloadObject.typ,
provider.name,
pldRequest.reason);
} else {
// The request is a payment request.
request = Cc["@mozilla.org/payment/request-payment-info;1"]
.createInstance(Ci.nsIDOMPaymentRequestPaymentInfo);
// The payment request should contain at least a 'name', 'description' and
// 'price' parameters.
if (!request || !pldRequest.name || !pldRequest.description ||
!pldRequest.price) {
debug("Not valid payment request");
return null;
}
// The payment request 'price' parameter is a collection of objects with
// 'country', 'currency' and 'amount' members.
let productPrices = [];
if (!Array.isArray(pldRequest.price)) {
pldRequest.price = [pldRequest.price];
}
for (let i in pldRequest.price) {
if (!pldRequest.price[i].country || !pldRequest.price[i].currency ||
!pldRequest.price[i].amount) {
debug("Not valid payment request. " +
"Price parameter is not well formed");
return null;
}
let price = Cc["@mozilla.org/payment/product-price;1"]
.createInstance(Ci.nsIDOMPaymentProductPrice);
price.wrappedJSObject.init(pldRequest.price[i].country,
pldRequest.price[i].currency,
pldRequest.price[i].amount);
productPrices.push(price);
}
request.wrappedJSObject.init(aJwt,
payloadObject.typ,
provider.name,
pldRequest.name,
pldRequest.description,
productPrices);
}
return request;
},
showPaymentFlow: function showPaymentFlow(aPaymentProvider, aJwt) {
let paymentFlowInfo = Cc["@mozilla.org/payment/flow-info;1"]
.createInstance(Ci.nsIPaymentFlowInfo);
paymentFlowInfo.uri = aPaymentProvider.uri;
paymentFlowInfo.requestMethod = aPaymentProvider.requestMethod;
paymentFlowInfo.jwt = aJwt;
let glue = Cc["@mozilla.org/payment/ui-glue;1"]
.createInstance(Ci.nsIPaymentUIGlue);
if (!glue) {
debug("Could not create nsIPaymentUIGlue instance");
this.paymentFailed("CREATE_PAYMENT_GLUE_FAILED");
return false;
}
glue.showPaymentFlow(paymentFlowInfo, this.paymentFailed);
},
// nsIObserver
observe: function observe(subject, topic, data) {
if (topic == "xpcom-shutdown") {
for each (let msgname in PAYMENT_IPC_MSG_NAMES) {
ppmm.removeMessageListener(msgname, this);
}
this.registeredProviders = null;
this.messageManagers = null;
Services.obs.removeObserver(this, "xpcom-shutdown");
}
},
};
PaymentManager.init();

View File

@ -1,3 +1,18 @@
component {a920adc0-c36e-4fd0-8de0-aac1ac6ebbd0} Payment.js
contract @mozilla.org/payment/content-helper;1 {a920adc0-c36e-4fd0-8de0-aac1ac6ebbd0}
category JavaScript-navigator-property mozPay @mozilla.org/payment/content-helper;1
component {b8bce4e7-fbf0-4719-a634-b1bf9018657c} PaymentFlowInfo.js
contract @mozilla.org/payment/flow-info;1 {b8bce4e7-fbf0-4719-a634-b1bf9018657c}
component {3d7dcabf-b77c-4bb3-b225-46011898ec32} PaymentRequestInfo.js
contract @mozilla.org/payment/product-price;1 {3d7dcabf-b77c-4bb3-b225-46011898ec32}
component {0a58c67d-f003-48da-81d1-bd8f605f4b1c} PaymentRequestInfo.js
contract @mozilla.org/payment/request-info;1 {0a58c67d-f003-48da-81d1-bd8f605f4b1c}
component {7f2e3274-3956-42e1-b7ce-59b8cd23d177} PaymentRequestInfo.js
contract @mozilla.org/payment/request-payment-info;1 {7f2e3274-3956-42e1-b7ce-59b8cd23d177}
component {e75566c6-dfb1-4f6b-b21d-078536c883b0} PaymentRequestInfo.js
contract @mozilla.org/payment/request-refund-info;1 {e75566c6-dfb1-4f6b-b21d-078536c883b0}

View File

@ -0,0 +1,22 @@
/* 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/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
function PaymentFlowInfo() {
};
PaymentFlowInfo.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPaymentFlowInfo]),
classID: Components.ID("{b8bce4e7-fbf0-4719-a634-b1bf9018657c}"),
uri: null,
jwt: null,
requestMethod: null
};
const NSGetFactory = XPCOMUtils.generateNSGetFactory([PaymentFlowInfo]);

View File

@ -0,0 +1,136 @@
/* 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/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const PAYMENTREQUESTINFO_CID =
Components.ID("{0a58c67d-f003-48da-81d1-bd8f605f4b1c}");
const PAYMENTPRODUCTPRICE_CID =
Components.ID("{3d7dcabf-b77c-4bb3-b225-46011898ec32}");
const PAYMENTREQUESTPAYMENTINFO_CID =
Components.ID("{7f2e3274-3956-42e1-b7ce-59b8cd23d177}");
const PAYMENTREQUESTREFUNDINFO_CID =
Components.ID("{e75566c6-dfb1-4f6b-b21d-078536c883b0}");
// nsIDOMPaymentProductPrice
function PaymentProductPrice() {
this.wrappedJSObject = this;
};
PaymentProductPrice.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMPaymentProductPrice]),
classID: PAYMENTPRODUCTPRICE_CID,
classInfo: XPCOMUtils.generateCI({
classID: PAYMENTPRODUCTPRICE_CID,
contractID: "@mozilla.org/payment/product-price;1",
classDescription: "Payment product price",
flags: Ci.nsIClassInfo.DOM_OBJECT,
interfaces: [Ci.nsIDOMPaymentProductPrice]
}),
country: null,
currency: null,
amount: null,
init: function init(aCountry, aCurrency, aAmount) {
this.country = aCountry;
this.currency = aCurrency;
this.amount = aAmount;
}
};
// nsIDOMPaymentRequestInfo
function PaymentRequestInfo() {
};
PaymentRequestInfo.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMPaymentRequestInfo]),
classID: PAYMENTREQUESTINFO_CID,
classInfo: XPCOMUtils.generateCI({
classID: PAYMENTREQUESTINFO_CID,
contractID: "@mozilla.org/payment/request-info;1",
classDescription: "Payment request information",
flags: Ci.nsIClassInfo.DOM_OBJECT,
interfaces: [Ci.nsIDOMPaymentRequestInfo]
}),
jwt: null,
type: null,
providerName: null,
initRequest: function initRequest(aJwt, aType, aProviderName) {
this.jwt = aJwt;
this.type = aType;
this.providerName = aProviderName;
}
};
// nsIDOMPaymentRequestPaymentInfo
function PaymentRequestPaymentInfo() {
this.wrappedJSObject = this;
};
PaymentRequestPaymentInfo.prototype = {
__proto__: PaymentRequestInfo.prototype,
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMPaymentRequestPaymentInfo]),
classID: PAYMENTREQUESTPAYMENTINFO_CID,
classInfo: XPCOMUtils.generateCI({
classID: PAYMENTREQUESTPAYMENTINFO_CID,
contractID: "@mozilla.org/payment/request-payment-info;1",
classDescription: "Payment request payment information",
flags: Ci.nsIClassInfo.DOM_OBJECT,
interfaces: [Ci.nsIDOMPaymentRequestPaymentInfo]
}),
productName: null,
productDescription: null,
productPrice: null,
init: function init(aJwt, aType, aProviderName,
aProductName, aProductDescription, aProductPrice) {
this.__proto__.initRequest.call(this, aJwt, aType, aProviderName);
this.productName = aProductName;
this.productDescription = aProductDescription;
this.productPrice = aProductPrice;
}
};
// nsIDOMPaymentRequestRefundInfo
function PaymentRequestRefundInfo() {
this.wrappedJSObject = this;
};
PaymentRequestRefundInfo.prototype = {
__proto__: PaymentRequestInfo.prototype,
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMPaymentRequestRefundInfo]),
classID: PAYMENTREQUESTREFUNDINFO_CID,
classInfo: XPCOMUtils.generateCI({
classID: PAYMENTREQUESTREFUNDINFO_CID,
contractID: "@mozilla.org/payment/request-refund-info;1",
classDescription: "Payment request refund information",
flags: Ci.nsIClassInfo.DOM_OBJECT,
interfaces: [Ci.nsIDOMPaymentRequestRefundInfo]
}),
reason: null,
init: function init(aJwt, aType, aProviderName, aReason) {
this.__proto__.initRequest.call(this, aJwt, aType, aProviderName);
this.reason = aReason;
}
};
const NSGetFactory = XPCOMUtils.generateNSGetFactory([
PaymentProductPrice,
PaymentRequestInfo,
PaymentRequestPaymentInfo,
PaymentRequestRefundInfo
]);

View File

@ -0,0 +1,20 @@
# 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/.
DEPTH = @DEPTH@
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
XPIDL_MODULE = dom_payment
XPIDLSRCS = nsIDOMNavigatorPayment.idl \
nsIDOMPaymentRequestInfo.idl \
nsIPaymentFlowInfo.idl \
nsIPaymentUIGlue.idl \
$(NULL)
include $(topsrcdir)/config/rules.mk