1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
9 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
10 Cu.import("resource://gre/modules/Services.jsm");
12 this.EXPORTED_SYMBOLS = [];
14 const PAYMENT_IPC_MSG_NAMES = ["Payment:Pay",
18 const PREF_PAYMENTPROVIDERS_BRANCH = "dom.payment.provider.";
19 const PREF_PAYMENT_BRANCH = "dom.payment.";
20 const PREF_DEBUG = "dom.payment.debug";
22 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
23 "@mozilla.org/parentprocessmessagemanager;1",
24 "nsIMessageListenerManager");
26 XPCOMUtils.defineLazyServiceGetter(this, "prefService",
27 "@mozilla.org/preferences-service;1",
30 let PaymentManager = {
31 init: function init() {
32 // Payment providers data are stored as a preference.
33 this.registeredProviders = null;
35 this.messageManagers = {};
37 // The dom.payment.skipHTTPSCheck pref is supposed to be used only during
38 // development process. This preference should not be active for a
40 let paymentPrefs = prefService.getBranch(PREF_PAYMENT_BRANCH);
41 this.checkHttps = true;
43 if (paymentPrefs.getPrefType("skipHTTPSCheck")) {
44 this.checkHttps = !paymentPrefs.getBoolPref("skipHTTPSCheck");
48 for each (let msgname in PAYMENT_IPC_MSG_NAMES) {
49 ppmm.addMessageListener(msgname, this);
52 Services.obs.addObserver(this, "xpcom-shutdown", false);
56 Services.prefs.getPrefType(PREF_DEBUG) == Ci.nsIPrefBranch.PREF_BOOL
57 && Services.prefs.getBoolPref(PREF_DEBUG);
64 * Process a message from the content process.
66 receiveMessage: function receiveMessage(aMessage) {
67 let name = aMessage.name;
68 let msg = aMessage.json;
70 this.LOG("Received '" + name + "' message from content process");
75 // First of all, we register the payment providers.
76 if (!this.registeredProviders) {
77 this.registeredProviders = {};
78 this.registerPaymentProviders();
81 // We save the message target message manager so we can later dispatch
82 // back messages without broadcasting to all child processes.
83 let requestId = msg.requestId;
84 this.messageManagers[requestId] = aMessage.target;
86 // We check the jwt type and look for a match within the
87 // registered payment providers to get the correct payment request
89 let paymentRequests = [];
91 for (let i in msg.jwts) {
92 let pr = this.getPaymentRequestInfo(requestId, msg.jwts[i]);
96 // We consider jwt type repetition an error.
97 if (jwtTypes[pr.type]) {
98 this.paymentFailed(requestId,
99 "PAY_REQUEST_ERROR_DUPLICATED_JWT_TYPE");
102 jwtTypes[pr.type] = true;
103 paymentRequests.push(pr);
106 if (!paymentRequests.length) {
107 this.paymentFailed(requestId,
108 "PAY_REQUEST_ERROR_NO_VALID_REQUEST_FOUND");
112 // After getting the list of valid payment requests, we ask the user
113 // for confirmation before sending any request to any payment provider.
114 // If there is more than one choice, we also let the user select the one
116 let glue = Cc["@mozilla.org/payment/ui-glue;1"]
117 .createInstance(Ci.nsIPaymentUIGlue);
120 this.LOG("Could not create nsIPaymentUIGlue instance");
122 this.paymentFailed(requestId,
123 "INTERNAL_ERROR_CREATE_PAYMENT_GLUE_FAILED");
127 let confirmPaymentSuccessCb = function successCb(aRequestId,
129 // Get the appropriate payment provider data based on user's choice.
130 let selectedProvider = this.registeredProviders[aResult];
131 if (!selectedProvider || !selectedProvider.uri) {
133 this.LOG("Could not retrieve a valid provider based on user's " +
136 this.paymentFailed(aRequestId,
137 "INTERNAL_ERROR_NO_VALID_SELECTED_PROVIDER");
142 for (let i in paymentRequests) {
143 if (paymentRequests[i].type == aResult) {
144 jwt = paymentRequests[i].jwt;
150 this.LOG("The selected request has no JWT information " +
153 this.paymentFailed(aRequestId,
154 "INTERNAL_ERROR_NO_JWT_ASSOCIATED_TO_REQUEST");
158 this.showPaymentFlow(aRequestId, selectedProvider, jwt);
161 let confirmPaymentErrorCb = this.paymentFailed;
163 glue.confirmPaymentRequest(requestId,
165 confirmPaymentSuccessCb.bind(this),
166 confirmPaymentErrorCb.bind(this));
169 case "Payment:Success":
170 case "Payment:Failed": {
171 let mm = this.messageManagers[msg.requestId];
172 mm.sendAsyncMessage(name, {
173 requestId: msg.requestId,
175 errorMsg: msg.errorMsg
183 * Helper function to register payment providers stored as preferences.
185 registerPaymentProviders: function registerPaymentProviders() {
186 let paymentProviders = prefService
187 .getBranch(PREF_PAYMENTPROVIDERS_BRANCH)
190 // First get the numbers of the providers by getting all ###.uri prefs.
192 for (let i in paymentProviders) {
193 let match = /^(\d+)\.uri$/.exec(paymentProviders[i]);
202 let appsService = Cc["@mozilla.org/AppsService;1"]
203 .getService(Ci.nsIAppsService);
204 let systemAppId = Ci.nsIScriptSecurityManager.NO_APP_ID;
207 let manifestURL = Services.prefs.getCharPref("b2g.system_manifest_url");
208 systemAppId = appsService.getAppLocalIdByManifestURL(manifestURL);
209 this.LOG("System app id=" + systemAppId);
213 // Now register the payment providers.
214 for (let i in nums) {
215 let branch = prefService
216 .getBranch(PREF_PAYMENTPROVIDERS_BRANCH + nums[i] + ".");
217 let vals = branch.getChildList("");
218 if (vals.length == 0) {
222 let type = branch.getCharPref("type");
223 if (type in this.registeredProviders) {
226 let provider = this.registeredProviders[type] = {
227 name: branch.getCharPref("name"),
228 uri: branch.getCharPref("uri"),
229 description: branch.getCharPref("description"),
230 requestMethod: branch.getCharPref("requestMethod")
234 // Let this payment provider access the firefox-accounts API when
235 // it's loaded in the trusted UI.
236 if (systemAppId != Ci.nsIScriptSecurityManager.NO_APP_ID) {
237 this.LOG("Granting firefox-accounts permission to " + provider.uri);
238 let uri = Services.io.newURI(provider.uri, null, null);
239 let principal = Services.scriptSecurityManager
240 .getAppCodebasePrincipal(uri, systemAppId, true);
242 Services.perms.addFromPrincipal(principal, "firefox-accounts",
243 Ci.nsIPermissionManager.ALLOW_ACTION,
244 Ci.nsIPermissionManager.EXPIRE_SESSION);
249 this.LOG("Registered Payment Providers: " +
250 JSON.stringify(this.registeredProviders[type]));
254 this.LOG("An error ocurred registering a payment provider. " + ex);
261 * Helper for sending a Payment:Failed message to the parent process.
263 paymentFailed: function paymentFailed(aRequestId, aErrorMsg) {
264 let mm = this.messageManagers[aRequestId];
265 mm.sendAsyncMessage("Payment:Failed", {
266 requestId: aRequestId,
272 * Helper function to get the payment request info according to the jwt
273 * type. Payment provider's data is stored as a preference.
275 getPaymentRequestInfo: function getPaymentRequestInfo(aRequestId, aJwt) {
277 this.paymentFailed(aRequestId, "INTERNAL_ERROR_CALL_WITH_MISSING_JWT");
281 // First thing, we check that the jwt type is an allowed type and has a
282 // payment provider flow information associated.
284 // A jwt string consists in three parts separated by period ('.'): header,
285 // payload and signature.
286 let segments = aJwt.split('.');
287 if (segments.length !== 3) {
289 this.LOG("Error getting payment provider's uri. " +
290 "Not enough or too many segments");
292 this.paymentFailed(aRequestId,
293 "PAY_REQUEST_ERROR_WRONG_SEGMENTS_COUNT");
299 // We only care about the payload segment, which contains the jwt type
300 // that should match with any of the stored payment provider's data and
301 // the payment request information to be shown to the user.
302 // Before decoding the JWT string we need to normalize it to be compliant
304 segments[1] = segments[1].replace("-", "+", "g").replace("_", "/", "g");
305 let payload = atob(segments[1]);
307 this.LOG("Payload " + payload);
309 if (!payload.length) {
310 this.paymentFailed(aRequestId, "PAY_REQUEST_ERROR_EMPTY_PAYLOAD");
313 payloadObject = JSON.parse(payload);
314 if (!payloadObject) {
315 this.paymentFailed(aRequestId,
316 "PAY_REQUEST_ERROR_ERROR_PARSING_JWT_PAYLOAD");
320 this.paymentFailed(aRequestId,
321 "PAY_REQUEST_ERROR_ERROR_DECODING_JWT");
325 if (!payloadObject.typ) {
326 this.paymentFailed(aRequestId,
327 "PAY_REQUEST_ERROR_NO_TYP_PARAMETER");
331 if (!payloadObject.request) {
332 this.paymentFailed(aRequestId,
333 "PAY_REQUEST_ERROR_NO_REQUEST_PARAMETER");
337 // Once we got the jwt 'typ' value we look for a match within the payment
338 // providers stored preferences. If the jwt 'typ' is not recognized as one
339 // of the allowed values for registered payment providers, we skip the jwt
340 // validation but we don't fire any error. This way developers might have
341 // a default set of well formed JWTs that might be used in different B2G
342 // devices with a different set of allowed payment providers.
343 let provider = this.registeredProviders[payloadObject.typ];
346 this.LOG("Not registered payment provider for jwt type: " +
352 if (!provider.uri || !provider.name) {
353 this.paymentFailed(aRequestId,
354 "INTERNAL_ERROR_WRONG_REGISTERED_PAY_PROVIDER");
358 // We only allow https for payment providers uris.
359 if (this.checkHttps && !/^https/.exec(provider.uri.toLowerCase())) {
360 // We should never get this far.
362 this.LOG("Payment provider uris must be https: " + provider.uri);
364 this.paymentFailed(aRequestId,
365 "INTERNAL_ERROR_NON_HTTPS_PROVIDER_URI");
369 let pldRequest = payloadObject.request;
370 return { jwt: aJwt, type: payloadObject.typ, providerName: provider.name };
373 showPaymentFlow: function showPaymentFlow(aRequestId,
376 let paymentFlowInfo = Cc["@mozilla.org/payment/flow-info;1"]
377 .createInstance(Ci.nsIPaymentFlowInfo);
378 paymentFlowInfo.uri = aPaymentProvider.uri;
379 paymentFlowInfo.requestMethod = aPaymentProvider.requestMethod;
380 paymentFlowInfo.jwt = aJwt;
381 paymentFlowInfo.name = aPaymentProvider.name;
382 paymentFlowInfo.description = aPaymentProvider.description;
384 let glue = Cc["@mozilla.org/payment/ui-glue;1"]
385 .createInstance(Ci.nsIPaymentUIGlue);
388 this.LOG("Could not create nsIPaymentUIGlue instance");
390 this.paymentFailed(aRequestId,
391 "INTERNAL_ERROR_CREATE_PAYMENT_GLUE_FAILED");
394 glue.showPaymentFlow(aRequestId,
396 this.paymentFailed.bind(this));
401 observe: function observe(subject, topic, data) {
402 if (topic == "xpcom-shutdown") {
403 for each (let msgname in PAYMENT_IPC_MSG_NAMES) {
404 ppmm.removeMessageListener(msgname, this);
406 this.registeredProviders = null;
407 this.messageManagers = null;
409 Services.obs.removeObserver(this, "xpcom-shutdown");
413 LOG: function LOG(s) {
417 dump("-*- PaymentManager: " + s + "\n");
421 PaymentManager.init();