michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const { classes: Cc, interfaces: Ci, utils: Cu } = Components; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/JNI.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "cpmm", michael@0: "@mozilla.org/childprocessmessagemanager;1", michael@0: "nsIMessageSender"); michael@0: michael@0: function paymentSuccess(aRequestId) { michael@0: return function(aResult) { michael@0: closePaymentTab(aRequestId, function() { michael@0: cpmm.sendAsyncMessage("Payment:Success", { result: aResult, michael@0: requestId: aRequestId }); michael@0: }); michael@0: } michael@0: } michael@0: michael@0: function paymentFailed(aRequestId) { michael@0: return function(aErrorMsg) { michael@0: closePaymentTab(aRequestId, function() { michael@0: cpmm.sendAsyncMessage("Payment:Failed", { errorMsg: aErrorMsg, michael@0: requestId: aRequestId }); michael@0: }); michael@0: } michael@0: } michael@0: michael@0: let paymentTabs = {}; michael@0: let cancelTabCallbacks = {}; michael@0: function paymentCanceled(aRequestId) { michael@0: return function() { michael@0: paymentFailed(aRequestId)(); michael@0: } michael@0: } michael@0: function closePaymentTab(aId, aCallback) { michael@0: if (paymentTabs[aId]) { michael@0: paymentTabs[aId].browser.removeEventListener("TabClose", cancelTabCallbacks[aId]); michael@0: delete cancelTabCallbacks[aId]; michael@0: michael@0: // We ask the UI to close the selected payment flow. michael@0: let content = Services.wm.getMostRecentWindow("navigator:browser"); michael@0: if (content) { michael@0: content.BrowserApp.closeTab(paymentTabs[aId]); michael@0: } michael@0: michael@0: paymentTabs[aId] = null; michael@0: } michael@0: michael@0: aCallback(); michael@0: } michael@0: michael@0: function PaymentUI() { michael@0: } michael@0: michael@0: PaymentUI.prototype = { michael@0: get bundle() { michael@0: delete this.bundle; michael@0: return this.bundle = Services.strings.createBundle("chrome://browser/locale/payments.properties"); michael@0: }, michael@0: michael@0: confirmPaymentRequest: function confirmPaymentRequest(aRequestId, michael@0: aRequests, michael@0: aSuccessCb, michael@0: aErrorCb) { michael@0: let _error = this._error(aErrorCb); michael@0: michael@0: let listItems = []; michael@0: michael@0: // If there's only one payment provider that will work, just move on without prompting the user. michael@0: if (aRequests.length == 1) { michael@0: aSuccessCb.onresult(aRequestId, aRequests[0].type); michael@0: return; michael@0: } michael@0: michael@0: // Otherwise, let the user select a payment provider from a list. michael@0: for (let i = 0; i < aRequests.length; i++) { michael@0: let request = aRequests[i]; michael@0: let requestText = request.providerName; michael@0: if (request.productPrice) { michael@0: requestText += " (" + request.productPrice[0].amount + " " + michael@0: request.productPrice[0].currency + ")"; michael@0: } michael@0: listItems.push({ label: requestText }); michael@0: } michael@0: michael@0: let p = new Prompt({ michael@0: window: null, michael@0: title: this.bundle.GetStringFromName("payments.providerdialog.title"), michael@0: }).setSingleChoiceItems(listItems).show(function(data) { michael@0: if (data.button > -1 && aSuccessCb) { michael@0: aSuccessCb.onresult(aRequestId, aRequests[data.button].type); michael@0: } else { michael@0: _error(aRequestId, "USER_CANCELED"); michael@0: } michael@0: }); michael@0: }, michael@0: michael@0: _error: function(aCallback) { michael@0: return function _error(id, msg) { michael@0: if (aCallback) { michael@0: aCallback.onresult(id, msg); michael@0: } michael@0: }; michael@0: }, michael@0: michael@0: showPaymentFlow: function showPaymentFlow(aRequestId, michael@0: aPaymentFlowInfo, michael@0: aErrorCb) { michael@0: let _error = this._error(aErrorCb); michael@0: michael@0: // We ask the UI to browse to the selected payment flow. michael@0: let content = Services.wm.getMostRecentWindow("navigator:browser"); michael@0: if (!content) { michael@0: _error(aRequestId, "NO_CONTENT_WINDOW"); michael@0: return; michael@0: } michael@0: michael@0: // TODO: For now, known payment providers (BlueVia and Mozilla Market) michael@0: // only accepts the JWT by GET, so we just add it to the URI. michael@0: // https://github.com/mozilla-b2g/gaia/blob/master/apps/system/js/payment.js michael@0: let tab = content.BrowserApp.addTab(aPaymentFlowInfo.uri + aPaymentFlowInfo.jwt); michael@0: michael@0: // Inject paymentSuccess and paymentFailed methods into the document after its loaded. michael@0: tab.browser.addEventListener("DOMWindowCreated", function loadPaymentShim() { michael@0: let frame = tab.browser.contentDocument.defaultView; michael@0: try { michael@0: frame.wrappedJSObject.mozPaymentProvider = { michael@0: __exposedProps__: { michael@0: paymentSuccess: 'r', michael@0: paymentFailed: 'r', michael@0: mnc: 'r', michael@0: mcc: 'r', michael@0: }, michael@0: michael@0: _getNetworkInfo: function(type) { michael@0: let jni = new JNI(); michael@0: let cls = jni.findClass("org/mozilla/gecko/GeckoNetworkManager"); michael@0: let method = jni.getStaticMethodID(cls, "get" + type.toUpperCase(), "()I"); michael@0: let val = jni.callStaticIntMethod(cls, method); michael@0: jni.close(); michael@0: michael@0: if (val < 0) michael@0: return null; michael@0: return val; michael@0: }, michael@0: michael@0: get mnc() { michael@0: delete this.mnc; michael@0: return this.mnc = this._getNetworkInfo("mnc"); michael@0: }, michael@0: michael@0: get mcc() { michael@0: delete this.mcc; michael@0: return this.mcc = this._getNetworkInfo("mcc"); michael@0: }, michael@0: michael@0: paymentSuccess: paymentSuccess(aRequestId), michael@0: paymentFailed: paymentFailed(aRequestId) michael@0: }; michael@0: } catch (e) { michael@0: _error(aRequestId, "ERROR_ADDING_METHODS"); michael@0: } finally { michael@0: tab.browser.removeEventListener("DOMWindowCreated", loadPaymentShim); michael@0: } michael@0: }, true); michael@0: michael@0: // Store a reference to the tab so that we can close it when the payment succeeds or fails. michael@0: paymentTabs[aRequestId] = tab; michael@0: cancelTabCallbacks[aRequestId] = paymentCanceled(aRequestId); michael@0: michael@0: // Fail the payment if the tab is closed on its own michael@0: tab.browser.addEventListener("TabClose", cancelTabCallbacks[aRequestId]); michael@0: }, michael@0: michael@0: cleanup: function cleanup() { michael@0: // Nothing to do here. michael@0: }, michael@0: michael@0: classID: Components.ID("{3c6c9575-f57e-427b-a8aa-57bc3cbff48f}"), michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIPaymentUIGlue]) michael@0: } michael@0: michael@0: this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PaymentUI]);