1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/components/PaymentsUI.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,190 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +const { classes: Cc, interfaces: Ci, utils: Cu } = Components; 1.11 + 1.12 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.13 +Cu.import("resource://gre/modules/Services.jsm"); 1.14 +Cu.import("resource://gre/modules/JNI.jsm"); 1.15 + 1.16 +XPCOMUtils.defineLazyServiceGetter(this, "cpmm", 1.17 + "@mozilla.org/childprocessmessagemanager;1", 1.18 + "nsIMessageSender"); 1.19 + 1.20 +function paymentSuccess(aRequestId) { 1.21 + return function(aResult) { 1.22 + closePaymentTab(aRequestId, function() { 1.23 + cpmm.sendAsyncMessage("Payment:Success", { result: aResult, 1.24 + requestId: aRequestId }); 1.25 + }); 1.26 + } 1.27 +} 1.28 + 1.29 +function paymentFailed(aRequestId) { 1.30 + return function(aErrorMsg) { 1.31 + closePaymentTab(aRequestId, function() { 1.32 + cpmm.sendAsyncMessage("Payment:Failed", { errorMsg: aErrorMsg, 1.33 + requestId: aRequestId }); 1.34 + }); 1.35 + } 1.36 +} 1.37 + 1.38 +let paymentTabs = {}; 1.39 +let cancelTabCallbacks = {}; 1.40 +function paymentCanceled(aRequestId) { 1.41 + return function() { 1.42 + paymentFailed(aRequestId)(); 1.43 + } 1.44 +} 1.45 +function closePaymentTab(aId, aCallback) { 1.46 + if (paymentTabs[aId]) { 1.47 + paymentTabs[aId].browser.removeEventListener("TabClose", cancelTabCallbacks[aId]); 1.48 + delete cancelTabCallbacks[aId]; 1.49 + 1.50 + // We ask the UI to close the selected payment flow. 1.51 + let content = Services.wm.getMostRecentWindow("navigator:browser"); 1.52 + if (content) { 1.53 + content.BrowserApp.closeTab(paymentTabs[aId]); 1.54 + } 1.55 + 1.56 + paymentTabs[aId] = null; 1.57 + } 1.58 + 1.59 + aCallback(); 1.60 +} 1.61 + 1.62 +function PaymentUI() { 1.63 +} 1.64 + 1.65 +PaymentUI.prototype = { 1.66 + get bundle() { 1.67 + delete this.bundle; 1.68 + return this.bundle = Services.strings.createBundle("chrome://browser/locale/payments.properties"); 1.69 + }, 1.70 + 1.71 + confirmPaymentRequest: function confirmPaymentRequest(aRequestId, 1.72 + aRequests, 1.73 + aSuccessCb, 1.74 + aErrorCb) { 1.75 + let _error = this._error(aErrorCb); 1.76 + 1.77 + let listItems = []; 1.78 + 1.79 + // If there's only one payment provider that will work, just move on without prompting the user. 1.80 + if (aRequests.length == 1) { 1.81 + aSuccessCb.onresult(aRequestId, aRequests[0].type); 1.82 + return; 1.83 + } 1.84 + 1.85 + // Otherwise, let the user select a payment provider from a list. 1.86 + for (let i = 0; i < aRequests.length; i++) { 1.87 + let request = aRequests[i]; 1.88 + let requestText = request.providerName; 1.89 + if (request.productPrice) { 1.90 + requestText += " (" + request.productPrice[0].amount + " " + 1.91 + request.productPrice[0].currency + ")"; 1.92 + } 1.93 + listItems.push({ label: requestText }); 1.94 + } 1.95 + 1.96 + let p = new Prompt({ 1.97 + window: null, 1.98 + title: this.bundle.GetStringFromName("payments.providerdialog.title"), 1.99 + }).setSingleChoiceItems(listItems).show(function(data) { 1.100 + if (data.button > -1 && aSuccessCb) { 1.101 + aSuccessCb.onresult(aRequestId, aRequests[data.button].type); 1.102 + } else { 1.103 + _error(aRequestId, "USER_CANCELED"); 1.104 + } 1.105 + }); 1.106 + }, 1.107 + 1.108 + _error: function(aCallback) { 1.109 + return function _error(id, msg) { 1.110 + if (aCallback) { 1.111 + aCallback.onresult(id, msg); 1.112 + } 1.113 + }; 1.114 + }, 1.115 + 1.116 + showPaymentFlow: function showPaymentFlow(aRequestId, 1.117 + aPaymentFlowInfo, 1.118 + aErrorCb) { 1.119 + let _error = this._error(aErrorCb); 1.120 + 1.121 + // We ask the UI to browse to the selected payment flow. 1.122 + let content = Services.wm.getMostRecentWindow("navigator:browser"); 1.123 + if (!content) { 1.124 + _error(aRequestId, "NO_CONTENT_WINDOW"); 1.125 + return; 1.126 + } 1.127 + 1.128 + // TODO: For now, known payment providers (BlueVia and Mozilla Market) 1.129 + // only accepts the JWT by GET, so we just add it to the URI. 1.130 + // https://github.com/mozilla-b2g/gaia/blob/master/apps/system/js/payment.js 1.131 + let tab = content.BrowserApp.addTab(aPaymentFlowInfo.uri + aPaymentFlowInfo.jwt); 1.132 + 1.133 + // Inject paymentSuccess and paymentFailed methods into the document after its loaded. 1.134 + tab.browser.addEventListener("DOMWindowCreated", function loadPaymentShim() { 1.135 + let frame = tab.browser.contentDocument.defaultView; 1.136 + try { 1.137 + frame.wrappedJSObject.mozPaymentProvider = { 1.138 + __exposedProps__: { 1.139 + paymentSuccess: 'r', 1.140 + paymentFailed: 'r', 1.141 + mnc: 'r', 1.142 + mcc: 'r', 1.143 + }, 1.144 + 1.145 + _getNetworkInfo: function(type) { 1.146 + let jni = new JNI(); 1.147 + let cls = jni.findClass("org/mozilla/gecko/GeckoNetworkManager"); 1.148 + let method = jni.getStaticMethodID(cls, "get" + type.toUpperCase(), "()I"); 1.149 + let val = jni.callStaticIntMethod(cls, method); 1.150 + jni.close(); 1.151 + 1.152 + if (val < 0) 1.153 + return null; 1.154 + return val; 1.155 + }, 1.156 + 1.157 + get mnc() { 1.158 + delete this.mnc; 1.159 + return this.mnc = this._getNetworkInfo("mnc"); 1.160 + }, 1.161 + 1.162 + get mcc() { 1.163 + delete this.mcc; 1.164 + return this.mcc = this._getNetworkInfo("mcc"); 1.165 + }, 1.166 + 1.167 + paymentSuccess: paymentSuccess(aRequestId), 1.168 + paymentFailed: paymentFailed(aRequestId) 1.169 + }; 1.170 + } catch (e) { 1.171 + _error(aRequestId, "ERROR_ADDING_METHODS"); 1.172 + } finally { 1.173 + tab.browser.removeEventListener("DOMWindowCreated", loadPaymentShim); 1.174 + } 1.175 + }, true); 1.176 + 1.177 + // Store a reference to the tab so that we can close it when the payment succeeds or fails. 1.178 + paymentTabs[aRequestId] = tab; 1.179 + cancelTabCallbacks[aRequestId] = paymentCanceled(aRequestId); 1.180 + 1.181 + // Fail the payment if the tab is closed on its own 1.182 + tab.browser.addEventListener("TabClose", cancelTabCallbacks[aRequestId]); 1.183 + }, 1.184 + 1.185 + cleanup: function cleanup() { 1.186 + // Nothing to do here. 1.187 + }, 1.188 + 1.189 + classID: Components.ID("{3c6c9575-f57e-427b-a8aa-57bc3cbff48f}"), 1.190 + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPaymentUIGlue]) 1.191 +} 1.192 + 1.193 +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PaymentUI]);