Bug 1097928 - Convert MozPaymentProvider to WebIDL. r=bholley,fabrice
authorFernando Jimenez <ferjmoreno@gmail.com>
Mon, 19 Jan 2015 14:50:32 +0100
changeset 224549 bbd37e7c0d082f380fcc1373fefa23491cd30da9
parent 224548 e8fd6f4fb702636e1ef8748f44ccdc8fa20db0ee
child 224550 6ea82f73b2b4332fb59c1a48f7cd73f82661454f
push id28135
push userphilringnalda@gmail.com
push dateTue, 20 Jan 2015 02:37:06 +0000
treeherdermozilla-central@2643429ae5ac [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbholley, fabrice
bugs1097928
milestone38.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1097928 - Convert MozPaymentProvider to WebIDL. r=bholley,fabrice
b2g/chrome/content/payment.js
b2g/chrome/jar.mn
b2g/components/B2GComponents.manifest
b2g/components/PaymentGlue.js
b2g/components/PaymentProviderStrategy.js
b2g/components/moz.build
b2g/installer/package-manifest.in
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
docshell/base/nsIDocShell.idl
dom/payment/Payment.manifest
dom/payment/PaymentProvider.js
dom/payment/PaymentProviderUtils.cpp
dom/payment/PaymentProviderUtils.h
dom/payment/interfaces/moz.build
dom/payment/interfaces/nsIPaymentProviderStrategy.idl
dom/payment/interfaces/nsIPaymentUIGlue.idl
dom/payment/moz.build
dom/payment/tests/mochitest/file_mozpayproviderchecker.html
dom/payment/tests/mochitest/file_payproviderfailure.html
dom/payment/tests/mochitest/file_payprovidersuccess.html
dom/payment/tests/mochitest/mochitest.ini
dom/payment/tests/mochitest/test_mozpay_callbacks.html
dom/payment/tests/mochitest/test_mozpaymentprovider.html
dom/webidl/MozPaymentProvider.webidl
dom/webidl/moz.build
testing/marionette/jar.mn
testing/specialpowers/content/MockPaymentsUIGlue.jsm
testing/specialpowers/content/specialpowersAPI.js
testing/specialpowers/jar.mn
deleted file mode 100644
--- a/b2g/chrome/content/payment.js
+++ /dev/null
@@ -1,484 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- /
-/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
-/* 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/. */
-
-// This JS shim contains the callbacks to fire DOMRequest events for
-// navigator.pay API within the payment processor's scope.
-
-"use strict";
-
-let { classes: Cc, interfaces: Ci, utils: Cu }  = Components;
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-
-const PREF_DEBUG = "dom.payment.debug";
-
-let _debug;
-try {
-  _debug = Services.prefs.getPrefType(PREF_DEBUG) == Ci.nsIPrefBranch.PREF_BOOL
-           && Services.prefs.getBoolPref(PREF_DEBUG);
-} catch(e){
-  _debug = false;
-}
-
-function LOG(s) {
-  if (!_debug) {
-    return;
-  }
-  dump("== Payment flow == " + s + "\n");
-}
-
-function LOGE(s) {
-  dump("== Payment flow ERROR == " + s + "\n");
-}
-
-if (_debug) {
-  LOG("Frame script injected");
-}
-
-XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
-                                   "@mozilla.org/childprocessmessagemanager;1",
-                                   "nsIMessageSender");
-
-XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
-                                   "@mozilla.org/uuid-generator;1",
-                                   "nsIUUIDGenerator");
-
-XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
-                                  "resource://gre/modules/SystemAppProxy.jsm");
-
-#ifdef MOZ_B2G_RIL
-XPCOMUtils.defineLazyServiceGetter(this, "gRil",
-                                   "@mozilla.org/ril;1",
-                                   "nsIRadioInterfaceLayer");
-
-XPCOMUtils.defineLazyServiceGetter(this, "iccProvider",
-                                   "@mozilla.org/ril/content-helper;1",
-                                   "nsIIccProvider");
-
-XPCOMUtils.defineLazyServiceGetter(this, "smsService",
-                                   "@mozilla.org/sms/smsservice;1",
-                                   "nsISmsService");
-
-XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
-                                   "@mozilla.org/settingsService;1",
-                                   "nsISettingsService");
-
-const kSilentSmsReceivedTopic = "silent-sms-received";
-const kMozSettingsChangedObserverTopic = "mozsettings-changed";
-
-const kRilDefaultDataServiceId = "ril.data.defaultServiceId";
-const kRilDefaultPaymentServiceId = "ril.payment.defaultServiceId";
-
-const MOBILEMESSAGECALLBACK_CID =
-  Components.ID("{b484d8c9-6be4-4f94-ab60-c9c7ebcc853d}");
-
-// In order to send messages through nsISmsService, we need to implement
-// nsIMobileMessageCallback, as the WebSMS API implementation is not usable
-// from JS.
-function SilentSmsRequest() {
-}
-
-SilentSmsRequest.prototype = {
-  __exposedProps__: {
-    onsuccess: "rw",
-    onerror: "rw"
-  },
-
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIMobileMessageCallback]),
-
-  classID: MOBILEMESSAGECALLBACK_CID,
-
-  set onsuccess(aSuccessCallback) {
-    this._onsuccess = aSuccessCallback;
-  },
-
-  set onerror(aErrorCallback) {
-    this._onerror = aErrorCallback;
-  },
-
-  notifyMessageSent: function notifyMessageSent(aMessage) {
-    if (_debug) {
-      LOG("Silent message successfully sent");
-    }
-    this._onsuccess(aMessage);
-  },
-
-  notifySendMessageFailed: function notifySendMessageFailed(aError) {
-    LOGE("Error sending silent message " + aError);
-    this._onerror(aError);
-  }
-};
-
-function PaymentSettings() {
-  Services.obs.addObserver(this, kMozSettingsChangedObserverTopic, false);
-
-  [kRilDefaultDataServiceId, kRilDefaultPaymentServiceId].forEach(setting => {
-    gSettingsService.createLock().get(setting, this);
-  });
-}
-
-PaymentSettings.prototype = {
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsISettingsServiceCallback,
-                                         Ci.nsIObserver]),
-
-  dataServiceId: 0,
-  _paymentServiceId: 0,
-
-  get paymentServiceId() {
-    return this._paymentServiceId;
-  },
-
-  set paymentServiceId(serviceId) {
-    // We allow the payment provider to set the service ID that will be used
-    // for the payment process.
-    // This service ID will be the one used by the silent SMS flow.
-    // If the payment is done with an external SIM, the service ID must be set
-    // to null.
-    if (serviceId != null && serviceId >= gRil.numRadioInterfaces) {
-      LOGE("Invalid service ID " + serviceId);
-      return;
-    }
-
-    gSettingsService.createLock().set(kRilDefaultPaymentServiceId,
-                                      serviceId, null);
-    this._paymentServiceId = serviceId;
-  },
-
-  setServiceId: function(aName, aValue) {
-    switch (aName) {
-      case kRilDefaultDataServiceId:
-        this.dataServiceId = aValue;
-        if (_debug) {
-          LOG("dataServiceId " + this.dataServiceId);
-        }
-        break;
-      case kRilDefaultPaymentServiceId:
-        this._paymentServiceId = aValue;
-        if (_debug) {
-          LOG("paymentServiceId " + this._paymentServiceId);
-        }
-        break;
-    }
-  },
-
-  handle: function(aName, aValue) {
-    if (aName != kRilDefaultDataServiceId) {
-      return;
-    }
-
-    this.setServiceId(aName, aValue);
-  },
-
-  observe: function(aSubject, aTopic, aData) {
-    if (aTopic != kMozSettingsChangedObserverTopic) {
-      return;
-    }
-
-    try {
-      if ('wrappedJSObject' in aSubject) {
-        aSubject = aSubject.wrappedJSObject;
-      }
-      if (!aSubject.key ||
-          (aSubject.key !== kRilDefaultDataServiceId &&
-           aSubject.key !== kRilDefaultPaymentServiceId)) {
-        return;
-      }
-      this.setServiceId(aSubject.key, aSubject.value);
-    } catch (e) {
-      LOGE(e);
-    }
-  },
-
-  cleanup: function() {
-    Services.obs.removeObserver(this, kMozSettingsChangedObserverTopic);
-  }
-};
-#endif
-
-const kClosePaymentFlowEvent = "close-payment-flow-dialog";
-
-let gRequestId;
-
-let PaymentProvider = {
-#ifdef MOZ_B2G_RIL
-  __exposedProps__: {
-    paymentSuccess: "r",
-    paymentFailed: "r",
-    paymentServiceId: "rw",
-    iccInfo: "r",
-    sendSilentSms: "r",
-    observeSilentSms: "r",
-    removeSilentSmsObserver: "r"
-  },
-#else
-  __exposedProps__: {
-    paymentSuccess: "r",
-    paymentFailed: "r"
-  },
-#endif
-
-  _init: function _init() {
-#ifdef MOZ_B2G_RIL
-    this._settings = new PaymentSettings();
-#endif
-  },
-
-  _closePaymentFlowDialog: function _closePaymentFlowDialog(aCallback) {
-    // After receiving the payment provider confirmation about the
-    // successful or failed payment flow, we notify the UI to close the
-    // payment flow dialog and return to the caller application.
-    let id = kClosePaymentFlowEvent + "-" + uuidgen.generateUUID().toString();
-
-    let detail = {
-      type: kClosePaymentFlowEvent,
-      id: id,
-      requestId: gRequestId
-    };
-
-    // In order to avoid race conditions, we wait for the UI to notify that
-    // it has successfully closed the payment flow and has recovered the
-    // caller app, before notifying the parent process to fire the success
-    // or error event over the DOMRequest.
-    SystemAppProxy.addEventListener("mozContentEvent",
-                               function closePaymentFlowReturn(evt) {
-      if (evt.detail.id == id && aCallback) {
-        aCallback();
-      }
-
-      SystemAppProxy.removeEventListener("mozContentEvent",
-                                  closePaymentFlowReturn);
-
-      let glue = Cc["@mozilla.org/payment/ui-glue;1"]
-                   .createInstance(Ci.nsIPaymentUIGlue);
-      glue.cleanup();
-    });
-
-    SystemAppProxy.dispatchEvent(detail);
-
-#ifdef MOZ_B2G_RIL
-    this._cleanUp();
-#endif
-  },
-
-  paymentSuccess: function paymentSuccess(aResult) {
-    if (_debug) {
-      LOG("paymentSuccess " + aResult);
-    }
-
-    PaymentProvider._closePaymentFlowDialog(function notifySuccess() {
-      if (!gRequestId) {
-        return;
-      }
-      cpmm.sendAsyncMessage("Payment:Success", { result: aResult,
-                                                 requestId: gRequestId });
-    });
-  },
-
-  paymentFailed: function paymentFailed(aErrorMsg) {
-    LOGE("paymentFailed " + aErrorMsg);
-
-    PaymentProvider._closePaymentFlowDialog(function notifyError() {
-      if (!gRequestId) {
-        return;
-      }
-      cpmm.sendAsyncMessage("Payment:Failed", { errorMsg: aErrorMsg,
-                                                requestId: gRequestId });
-    });
-  },
-
-#ifdef MOZ_B2G_RIL
-  get paymentServiceId() {
-    return this._settings.paymentServiceId;
-  },
-
-  set paymentServiceId(serviceId) {
-    this._settings.paymentServiceId = serviceId;
-  },
-
-  // We expose to the payment provider the information of all the SIMs
-  // available in the device. iccInfo is an object of this form:
-  //  {
-  //    "serviceId1": {
-  //       mcc: <string>,
-  //       mnc: <string>,
-  //       iccId: <string>,
-  //       dataPrimary: <boolean>
-  //     },
-  //    "serviceIdN": {...}
-  //  }
-  get iccInfo() {
-    if (!this._iccInfo) {
-      this._iccInfo = {};
-      for (let i = 0; i < gRil.numRadioInterfaces; i++) {
-        let info = iccProvider.getIccInfo(i);
-        if (!info) {
-          LOGE("Tried to get the ICC info for an invalid service ID " + i);
-          continue;
-        }
-
-        this._iccInfo[i] = {
-          iccId: info.iccid,
-          mcc: info.mcc,
-          mnc: info.mnc,
-          dataPrimary: i == this._settings.dataServiceId
-        };
-      }
-    }
-
-    return Cu.cloneInto(this._iccInfo, content);
-  },
-
-  _silentNumbers: null,
-  _silentSmsObservers: null,
-
-  sendSilentSms: function sendSilentSms(aNumber, aMessage) {
-    if (_debug) {
-      LOG("Sending silent message " + aNumber + " - " + aMessage);
-    }
-
-    let request = new SilentSmsRequest();
-
-    if (this._settings.paymentServiceId === null) {
-      LOGE("No payment service ID set. Cannot send silent SMS");
-      let runnable = {
-        run: function run() {
-          request.notifySendMessageFailed("NO_PAYMENT_SERVICE_ID");
-        }
-      };
-      Services.tm.currentThread.dispatch(runnable,
-                                         Ci.nsIThread.DISPATCH_NORMAL);
-      return request;
-    }
-
-    smsService.send(this._settings.paymentServiceId, aNumber, aMessage, true,
-                    request);
-    return request;
-  },
-
-  observeSilentSms: function observeSilentSms(aNumber, aCallback) {
-    if (_debug) {
-      LOG("observeSilentSms " + aNumber);
-    }
-
-    if (!this._silentSmsObservers) {
-      this._silentSmsObservers = {};
-      this._silentNumbers = [];
-      Services.obs.addObserver(this._onSilentSms.bind(this),
-                               kSilentSmsReceivedTopic,
-                               false);
-    }
-
-    if (!this._silentSmsObservers[aNumber]) {
-      this._silentSmsObservers[aNumber] = [];
-      this._silentNumbers.push(aNumber);
-      smsService.addSilentNumber(aNumber);
-    }
-
-    if (this._silentSmsObservers[aNumber].indexOf(aCallback) == -1) {
-      this._silentSmsObservers[aNumber].push(aCallback);
-    }
-  },
-
-  removeSilentSmsObserver: function removeSilentSmsObserver(aNumber, aCallback) {
-    if (_debug) {
-      LOG("removeSilentSmsObserver " + aNumber);
-    }
-
-    if (!this._silentSmsObservers || !this._silentSmsObservers[aNumber]) {
-      if (_debug) {
-        LOG("No observers for " + aNumber);
-      }
-      return;
-    }
-
-    let index = this._silentSmsObservers[aNumber].indexOf(aCallback);
-    if (index != -1) {
-      this._silentSmsObservers[aNumber].splice(index, 1);
-      if (this._silentSmsObservers[aNumber].length == 0) {
-        this._silentSmsObservers[aNumber] = null;
-        this._silentNumbers.splice(this._silentNumbers.indexOf(aNumber), 1);
-        smsService.removeSilentNumber(aNumber);
-      }
-    } else if (_debug) {
-      LOG("No callback found for " + aNumber);
-    }
-  },
-
-  _onSilentSms: function _onSilentSms(aSubject, aTopic, aData) {
-    if (_debug) {
-      LOG("Got silent message! " + aSubject.sender + " - " + aSubject.body);
-    }
-
-    let number = aSubject.sender;
-    if (!number || this._silentNumbers.indexOf(number) == -1) {
-      if (_debug) {
-        LOG("No observers for " + number);
-      }
-      return;
-    }
-
-    // If the service ID is null it means that the payment provider asked the
-    // user for her MSISDN, so we are in a MT only SMS auth flow. In this case
-    // we manually set the service ID to the one corresponding with the SIM
-    // that received the SMS.
-    if (this._settings.paymentServiceId === null) {
-      let i = 0;
-      while(i < gRil.numRadioInterfaces) {
-        if (this.iccInfo[i].iccId === aSubject.iccId) {
-          this._settings.paymentServiceId = i;
-          break;
-        }
-        i++;
-      }
-    }
-
-    this._silentSmsObservers[number].forEach(function(callback) {
-      callback(aSubject);
-    });
-  },
-
-  _cleanUp: function _cleanUp() {
-    if (_debug) {
-      LOG("Cleaning up!");
-    }
-
-    if (!this._silentNumbers) {
-      return;
-    }
-
-    while (this._silentNumbers.length) {
-      let number = this._silentNumbers.pop();
-      smsService.removeSilentNumber(number);
-    }
-    this._silentNumbers = null;
-    this._silentSmsObservers = null;
-    this._settings.cleanup();
-    Services.obs.removeObserver(this._onSilentSms, kSilentSmsReceivedTopic);
-  }
-#endif
-};
-
-// We save the identifier of the DOM request, so we can dispatch the results
-// of the payment flow to the appropriate content process.
-addMessageListener("Payment:LoadShim", function receiveMessage(aMessage) {
-  gRequestId = aMessage.json.requestId;
-  PaymentProvider._init();
-});
-
-addEventListener("DOMWindowCreated", function(e) {
-  content.wrappedJSObject.mozPaymentProvider = PaymentProvider;
-});
-
-#ifdef MOZ_B2G_RIL
-// If the trusted dialog is not closed via paymentSuccess or paymentFailed
-// a mozContentEvent with type 'cancel' is sent from the UI. We need to listen
-// for this event to clean up the silent sms observers if any exists.
-SystemAppProxy.addEventListener("mozContentEvent", function(e) {
-  if (e.detail.type === "cancel") {
-    PaymentProvider._cleanUp();
-  }
-});
-#endif
--- a/b2g/chrome/jar.mn
+++ b/b2g/chrome/jar.mn
@@ -27,17 +27,16 @@ chrome.jar:
 #ifndef MOZ_WIDGET_GONK
   content/desktop.js                    (content/desktop.js)
   content/screen.js                     (content/screen.js)
   content/runapp.js                     (content/runapp.js)
 #endif
 * content/content.css                   (content/content.css)
   content/touchcontrols.css             (content/touchcontrols.css)
 
-* content/payment.js                    (content/payment.js)
   content/identity.js                   (content/identity.js)
 
 % override chrome://global/skin/media/videocontrols.css chrome://b2g/content/touchcontrols.css
 % override chrome://global/content/aboutCertError.xhtml chrome://b2g/content/aboutCertError.xhtml
 % override chrome://global/skin/netError.css chrome://b2g/content/netError.css
 
   content/ErrorPage.js                  (content/ErrorPage.js)
   content/aboutCertError.xhtml          (content/aboutCertError.xhtml)
--- a/b2g/components/B2GComponents.manifest
+++ b/b2g/components/B2GComponents.manifest
@@ -37,19 +37,21 @@ component {1a94c87a-5ece-4d11-91e1-d29c2
 contract @mozilla.org/b2g-process-global;1 {1a94c87a-5ece-4d11-91e1-d29c29f21b28}
 category app-startup ProcessGlobal service,@mozilla.org/b2g-process-global;1
 
 # OMAContentHandler.js
 component {a6b2ab13-9037-423a-9897-dde1081be323} OMAContentHandler.js
 contract @mozilla.org/uriloader/content-handler;1?type=application/vnd.oma.drm.message {a6b2ab13-9037-423a-9897-dde1081be323}
 contract @mozilla.org/uriloader/content-handler;1?type=application/vnd.oma.dd+xml {a6b2ab13-9037-423a-9897-dde1081be323}
 
-# PaymentGlue.js
+# Payments
 component {8b83eabc-7929-47f4-8b48-4dea8d887e4b} PaymentGlue.js
 contract @mozilla.org/payment/ui-glue;1 {8b83eabc-7929-47f4-8b48-4dea8d887e4b}
+component {4834b2e1-2c91-44ea-b020-e2581ed279a4} PaymentProviderStrategy.js
+contract @mozilla.org/payment/provider-strategy;1 {4834b2e1-2c91-44ea-b020-e2581ed279a4}
 
 # TelProtocolHandler.js
 component {782775dd-7351-45ea-aff1-0ffa872cfdd2} TelProtocolHandler.js
 contract @mozilla.org/network/protocol;1?name=tel {782775dd-7351-45ea-aff1-0ffa872cfdd2}
 
 # SmsProtocolHandler.js
 component {81ca20cb-0dad-4e32-8566-979c8998bd73} SmsProtocolHandler.js
 contract @mozilla.org/network/protocol;1?name=sms {81ca20cb-0dad-4e32-8566-979c8998bd73}
--- a/b2g/components/PaymentGlue.js
+++ b/b2g/components/PaymentGlue.js
@@ -3,24 +3,25 @@
  * 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");
-
-// JS shim that contains the callback functions to be triggered from the
-// payment provider's code in order to fire DOMRequest events.
-const kPaymentShimFile = "chrome://b2g/content/payment.js";
+Cu.import("resource://gre/modules/Promise.jsm");
 
 // Type of MozChromEvents to handle payment dialogs.
 const kOpenPaymentConfirmationEvent = "open-payment-confirmation-dialog";
 const kOpenPaymentFlowEvent = "open-payment-flow-dialog";
+const kClosePaymentFlowEvent = "close-payment-flow-dialog";
+
+// Observer notification topic for payment flow cancelation.
+const kPaymentFlowCancelled = "payment-flow-cancelled";
 
 const PREF_DEBUG = "dom.payment.debug";
 
 XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
 XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
@@ -80,129 +81,122 @@ PaymentUI.prototype = {
     SystemAppProxy.addEventListener("mozContentEvent", this._handleSelection);
 
     SystemAppProxy.dispatchEvent(detail);
   },
 
   showPaymentFlow: function showPaymentFlow(aRequestId,
                                             aPaymentFlowInfo,
                                             aErrorCb) {
-    let _error = function _error(errorMsg) {
+    let _error = (errorMsg) => {
       if (aErrorCb) {
         aErrorCb.onresult(aRequestId, errorMsg);
       }
     };
 
     // We ask the UI to browse to the selected payment flow.
     let id = kOpenPaymentFlowEvent + "-" + this.getRandomId();
     let detail = {
       type: kOpenPaymentFlowEvent,
       id: id,
-      requestId: aRequestId,
-      uri: aPaymentFlowInfo.uri,
-      method: aPaymentFlowInfo.requestMethod,
-      jwt: aPaymentFlowInfo.jwt
+      requestId: aRequestId
     };
 
-    // At some point the UI would send the created iframe back so the
-    // callbacks for firing DOMRequest events can be loaded on its
-    // content.
-    this._loadPaymentShim = (function _loadPaymentShim(evt) {
-      let msg = evt.detail;
-      if (msg.id != id) {
-        return;
-      }
-
-      if (msg.errorMsg) {
-        SystemAppProxy.removeEventListener("mozContentEvent", this._loadPaymentShim);
-        this._loadPaymentShim = null;
-        _error("ERROR_LOADING_PAYMENT_SHIM: " + msg.errorMsg);
+    this._setPaymentRequest = (event) => {
+      let message = event.detail;
+      if (message.id != id) {
         return;
       }
 
-      if (!msg.frame) {
-        SystemAppProxy.removeEventListener("mozContentEvent", this._loadPaymentShim);
-        this._loadPaymentShim = null;
-        _error("ERROR_LOADING_PAYMENT_SHIM");
-        return;
-      }
+      let frame = message.frame;
+      let docshell = frame.contentWindow
+                          .QueryInterface(Ci.nsIInterfaceRequestor)
+                          .getInterface(Ci.nsIWebNavigation)
+                          .QueryInterface(Ci.nsIDocShell);
+      docshell.paymentRequestId = aRequestId;
+      frame.src = aPaymentFlowInfo.uri + aPaymentFlowInfo.jwt;
+      SystemAppProxy.removeEventListener("mozContentEvent",
+                                         this._setPaymentRequest);
+    };
+    SystemAppProxy.addEventListener("mozContentEvent",
+                                    this._setPaymentRequest);
 
-      // Try to load the payment shim file containing the payment callbacks
-      // in the content script.
-      let frame = msg.frame;
-      let frameLoader = frame.QueryInterface(Ci.nsIFrameLoaderOwner)
-                             .frameLoader;
-      let mm = frameLoader.messageManager;
-      try {
-        mm.loadFrameScript(kPaymentShimFile, true, true);
-        mm.sendAsyncMessage("Payment:LoadShim", { requestId: aRequestId });
-      } catch (e) {
-        if (this._debug) {
-          this.LOG("Error loading " + kPaymentShimFile + " as a frame script: "
-                    + e);
-        }
-        _error("ERROR_LOADING_PAYMENT_SHIM");
-      } finally {
-        SystemAppProxy.removeEventListener("mozContentEvent", this._loadPaymentShim);
-        this._loadPaymentShim = null;
-      }
-    }).bind(this);
-    SystemAppProxy.addEventListener("mozContentEvent", this._loadPaymentShim);
-
-    // We also listen for UI notifications about a closed payment flow. The UI
+    // We listen for UI notifications about a closed payment flow. The UI
     // should provide the reason of the closure within the 'errorMsg' parameter
-    this._notifyPayFlowClosed = (function _notifyPayFlowClosed(evt) {
+    this._notifyPayFlowClosed = (evt) => {
       let msg = evt.detail;
       if (msg.id != id) {
         return;
       }
 
       if (msg.type != 'cancel') {
         return;
       }
 
       if (msg.errorMsg) {
         _error(msg.errorMsg);
       }
+
       SystemAppProxy.removeEventListener("mozContentEvent",
                                          this._notifyPayFlowClosed);
       this._notifyPayFlowClosed = null;
-    }).bind(this);
+
+      Services.obs.notifyObservers(null, kPaymentFlowCancelled, null);
+    };
     SystemAppProxy.addEventListener("mozContentEvent",
-                               this._notifyPayFlowClosed);
+                                    this._notifyPayFlowClosed);
 
     SystemAppProxy.dispatchEvent(detail);
   },
 
+  closePaymentFlow: function(aRequestId) {
+    return new Promise((aResolve) => {
+      // After receiving the payment provider confirmation about the
+      // successful or failed payment flow, we notify the UI to close the
+      // payment flow dialog and return to the caller application.
+      let id = kClosePaymentFlowEvent + "-" + uuidgen.generateUUID().toString();
+
+      let detail = {
+        type: kClosePaymentFlowEvent,
+        id: id,
+        requestId: aRequestId
+      };
+
+      // In order to avoid race conditions, we wait for the UI to notify that
+      // it has successfully closed the payment flow and has recovered the
+      // caller app, before notifying the parent process to fire the success
+      // or error event over the DOMRequest.
+      SystemAppProxy.addEventListener("mozContentEvent",
+                                      (function closePaymentFlowReturn() {
+        SystemAppProxy.removeEventListener("mozContentEvent",
+                                    closePaymentFlowReturn);
+        this.cleanup();
+        aResolve();
+      }).bind(this));
+
+      SystemAppProxy.dispatchEvent(detail);
+    });
+  },
+
   cleanup: function cleanup() {
     if (this._handleSelection) {
-      SystemAppProxy.removeEventListener("mozContentEvent", this._handleSelection);
+      SystemAppProxy.removeEventListener("mozContentEvent",
+                                         this._handleSelection);
       this._handleSelection = null;
     }
 
     if (this._notifyPayFlowClosed) {
-      SystemAppProxy.removeEventListener("mozContentEvent", this._notifyPayFlowClosed);
+      SystemAppProxy.removeEventListener("mozContentEvent",
+                                         this._notifyPayFlowClosed);
       this._notifyPayFlowClosed = null;
     }
-
-    if (this._loadPaymentShim) {
-      SystemAppProxy.removeEventListener("mozContentEvent", this._loadPaymentShim);
-      this._loadPaymentShim = null;
-    }
   },
 
   getRandomId: function getRandomId() {
     return uuidgen.generateUUID().toString();
   },
 
-  LOG: function LOG(s) {
-    if (!this._debug) {
-      return;
-    }
-    dump("-*- PaymentGlue: " + s + "\n");
-  },
-
   classID: Components.ID("{8b83eabc-7929-47f4-8b48-4dea8d887e4b}"),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIPaymentUIGlue])
 }
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PaymentUI]);
new file mode 100644
--- /dev/null
+++ b/b2g/components/PaymentProviderStrategy.js
@@ -0,0 +1,177 @@
+/* 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;
+
+const PREF_DEBUG = "dom.payment.debug";
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "iccProvider",
+                                   "@mozilla.org/ril/content-helper;1",
+                                   "nsIIccProvider");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gRil",
+                                   "@mozilla.org/ril;1",
+                                   "nsIRadioInterfaceLayer");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
+                                   "@mozilla.org/settingsService;1",
+                                   "nsISettingsService");
+
+const kMozSettingsChangedObserverTopic = "mozsettings-changed";
+const kRilDefaultDataServiceId = "ril.data.defaultServiceId";
+const kRilDefaultPaymentServiceId = "ril.payment.defaultServiceId";
+
+let _debug;
+try {
+  _debug = Services.prefs.getPrefType(PREF_DEBUG) == Ci.nsIPrefBranch.PREF_BOOL
+           && Services.prefs.getBoolPref(PREF_DEBUG);
+} catch(e){
+  _debug = false;
+}
+
+function LOG(s) {
+  if (!_debug) {
+    return;
+  }
+  dump("== Payment Provider == " + s + "\n");
+}
+
+function LOGE(s) {
+  dump("== Payment Provider ERROR == " + s + "\n");
+}
+
+function PaymentSettings() {
+  Services.obs.addObserver(this, kMozSettingsChangedObserverTopic, false);
+
+  [kRilDefaultDataServiceId, kRilDefaultPaymentServiceId].forEach(setting => {
+    gSettingsService.createLock().get(setting, this);
+  });
+}
+
+PaymentSettings.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISettingsServiceCallback,
+                                         Ci.nsIObserver]),
+
+  dataServiceId: 0,
+  _paymentServiceId: 0,
+
+  get paymentServiceId() {
+    return this._paymentServiceId;
+  },
+
+  set paymentServiceId(serviceId) {
+    // We allow the payment provider to set the service ID that will be used
+    // for the payment process.
+    // This service ID will be the one used by the silent SMS flow.
+    // If the payment is done with an external SIM, the service ID must be set
+    // to null.
+    if (serviceId != null && serviceId >= gRil.numRadioInterfaces) {
+      LOGE("Invalid service ID " + serviceId);
+      return;
+    }
+
+    gSettingsService.createLock().set(kRilDefaultPaymentServiceId,
+                                      serviceId, 0);
+    this._paymentServiceId = serviceId;
+  },
+
+  setServiceId: function(aName, aValue) {
+    switch (aName) {
+      case kRilDefaultDataServiceId:
+        this.dataServiceId = aValue;
+        if (_debug) {
+          LOG("dataServiceId " + this.dataServiceId);
+        }
+        break;
+      case kRilDefaultPaymentServiceId:
+        this._paymentServiceId = aValue;
+        if (_debug) {
+          LOG("paymentServiceId " + this._paymentServiceId);
+        }
+        break;
+    }
+  },
+
+  handle: function(aName, aValue) {
+    if (aName != kRilDefaultDataServiceId) {
+      return;
+    }
+
+    this.setServiceId(aName, aValue);
+  },
+
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic != kMozSettingsChangedObserverTopic) {
+      return;
+    }
+
+    try {
+      if ("wrappedJSObject" in aSubject) {
+        aSubject = aSubject.wrappedJSObject;
+      }
+      if (!aSubject.key ||
+          (aSubject.key !== kRilDefaultDataServiceId &&
+           aSubject.key !== kRilDefaultPaymentServiceId)) {
+        return;
+      }
+      this.setServiceId(aSubject.key, aSubject.value);
+    } catch (e) {
+      LOGE(e);
+    }
+  },
+
+  cleanup: function() {
+    Services.obs.removeObserver(this, kMozSettingsChangedObserverTopic);
+  }
+};
+
+function PaymentProviderStrategy() {
+  this._settings = new PaymentSettings();
+}
+
+PaymentProviderStrategy.prototype = {
+  get paymentServiceId() {
+    return this._settings.paymentServiceId;
+  },
+
+  set paymentServiceId(aServiceId) {
+    this._settings.paymentServiceId = aServiceId;
+  },
+
+  get iccInfo() {
+    if (!this._iccInfo) {
+      this._iccInfo = [];
+      for (let i = 0; i < gRil.numRadioInterfaces; i++) {
+        let info = iccProvider.getIccInfo(i);
+        if (!info) {
+          LOGE("Tried to get the ICC info for an invalid service ID " + i);
+          continue;
+        }
+
+        this._iccInfo.push({
+          iccId: info.iccid,
+          mcc: info.mcc,
+          mnc: info.mnc,
+          dataPrimary: i == this._settings.dataServiceId
+        });
+      }
+    }
+    return this._iccInfo;
+  },
+
+  cleanup: function() {
+    this._settings.cleanup();
+  },
+
+  classID: Components.ID("{4834b2e1-2c91-44ea-b020-e2581ed279a4}"),
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPaymentProviderStrategy])
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PaymentProviderStrategy]);
--- a/b2g/components/moz.build
+++ b/b2g/components/moz.build
@@ -16,16 +16,17 @@ EXTRA_COMPONENTS += [
     'FilePicker.js',
     'FxAccountsUIGlue.js',
     'HelperAppDialog.js',
     'InterAppCommUIGlue.js',
     'MailtoProtocolHandler.js',
     'MobileIdentityUIGlue.js',
     'OMAContentHandler.js',
     'PaymentGlue.js',
+    'PaymentProviderStrategy.js',
     'ProcessGlobal.js',
     'SmsProtocolHandler.js',
     'SystemMessageGlue.js',
     'TelProtocolHandler.js',
     'WebappsUpdateTimer.js',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'gonk':
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -599,16 +599,17 @@
 
 @BINPATH@/components/TCPSocket.js
 @BINPATH@/components/TCPServerSocket.js
 @BINPATH@/components/TCPSocketParentIntermediary.js
 @BINPATH@/components/TCPSocket.manifest
 
 @BINPATH@/components/Payment.js
 @BINPATH@/components/PaymentFlowInfo.js
+@BINPATH@/components/PaymentProvider.js
 @BINPATH@/components/Payment.manifest
 
 @BINPATH@/components/DownloadsAPI.js
 @BINPATH@/components/DownloadsAPI.manifest
 
 ; InputMethod API
 @BINPATH@/components/MozKeyboard.js
 @BINPATH@/components/InputMethod.manifest
@@ -849,16 +850,17 @@ bin/components/@DLL_PREFIX@nkgnomevfs@DL
 @BINPATH@/components/UpdatePrompt.js
 #endif
 @BINPATH@/components/WebappsUpdateTimer.js
 @BINPATH@/components/DirectoryProvider.js
 @BINPATH@/components/ActivitiesGlue.js
 @BINPATH@/components/ProcessGlobal.js
 @BINPATH@/components/OMAContentHandler.js
 @BINPATH@/components/PaymentGlue.js
+@BINPATH@/components/PaymentProviderStrategy.js
 @BINPATH@/components/RecoveryService.js
 @BINPATH@/components/MailtoProtocolHandler.js
 @BINPATH@/components/SmsProtocolHandler.js
 @BINPATH@/components/TelProtocolHandler.js
 @BINPATH@/components/B2GAboutRedirector.js
 @BINPATH@/components/FilePicker.js
 @BINPATH@/components/HelperAppDialog.js
 @BINPATH@/components/DownloadsUI.js
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -13762,8 +13762,40 @@ nsDocShell::MaybeNotifyKeywordSearchLoad
         // Note that "keyword-search" refers to a search via the url
         // bar, not a bookmarks keyword search.
         obsSvc->NotifyObservers(searchEngine, "keyword-search", aKeyword.get());
       }
     }
   }
 #endif
 }
+
+NS_IMETHODIMP
+nsDocShell::SetPaymentRequestId(const nsAString& aPaymentRequestId)
+{
+    mPaymentRequestId = aPaymentRequestId;
+    return NS_OK;
+}
+
+nsString
+nsDocShell::GetInheritedPaymentRequestId()
+{
+    if (!mPaymentRequestId.IsEmpty()) {
+      return mPaymentRequestId;
+    }
+
+    nsCOMPtr<nsIDocShellTreeItem> parentAsItem;
+    GetSameTypeParent(getter_AddRefs(parentAsItem));
+
+    nsCOMPtr<nsIDocShell> parent = do_QueryInterface(parentAsItem);
+    if (!parent) {
+      return mPaymentRequestId;
+    }
+    return static_cast<nsDocShell*>(
+        parent.get())->GetInheritedPaymentRequestId();
+}
+
+NS_IMETHODIMP
+nsDocShell::GetPaymentRequestId(nsAString& aPaymentRequestId)
+{
+    aPaymentRequestId = GetInheritedPaymentRequestId();
+    return NS_OK;
+}
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -942,16 +942,19 @@ protected:
     // inside an app, we'll retrieve the containing app-id by walking up the
     // docshell hierarchy.
     //
     // (This needs to be the docshell's own /or containing/ app id because the
     // containing app frame might be in another process, in which case we won't
     // find it by walking up the docshell hierarchy.)
     uint32_t mOwnOrContainingAppId;
 
+    nsString mPaymentRequestId;
+
+    nsString GetInheritedPaymentRequestId();
 private:
     nsCString         mForcedCharset;
     nsCString         mParentCharset;
     int32_t           mParentCharsetSource;
     nsCOMPtr<nsIPrincipal> mParentCharsetPrincipal;
     nsTObserverArray<nsWeakPtr> mPrivacyObservers;
     nsTObserverArray<nsWeakPtr> mReflowObservers;
     nsTObserverArray<nsWeakPtr> mScrollObservers;
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -49,17 +49,17 @@ interface nsIWebBrowserPrint;
 interface nsIVariant;
 interface nsIPrivacyTransitionObserver;
 interface nsIReflowObserver;
 interface nsIScrollObserver;
 interface nsITabParent;
  
 typedef unsigned long nsLoadFlags;
 
-[scriptable, builtinclass, uuid(fef3bae1-6673-4c49-9f5a-fcc075926730)]
+[scriptable, builtinclass, uuid(e0e833fe-3a5b-48b0-8684-a097e09c0723)]
 interface nsIDocShell : nsIDocShellTreeItem
 {
   /**
    * Loads a given URI.  This will give priority to loading the requested URI
    * in the object implementing	this interface.  If it can't be loaded here
    * however, the URL dispatcher will go through its normal process of content
    * loading.
    *
@@ -768,17 +768,17 @@ interface nsIDocShell : nsIDocShellTreeI
    * this process until we hit an <iframe mozapp> or <iframe mozbrowser> (or
    * until the hierarchy ends).  Return true iff the docshell we stopped on has
    * isBrowserElement == true.
    */
   [infallible] readonly attribute boolean isInBrowserElement;
 
   /**
    * Returns true if this docshell corresponds to an <iframe mozbrowser> or
-   * <iframe mozap>, or if this docshell is contained in an <iframe mozbrowser>
+   * <iframe mozapp>, or if this docshell is contained in an <iframe mozbrowser>
    * or <iframe mozapp>.
    *
    * To compute this value, we walk up the docshell hierarchy.  If we encounter
    * a docshell with isBrowserElement or isApp before we hit the end of the
    * hierarchy, we return true.  Otherwise, we return false.
    */
   [infallible] readonly attribute boolean isInBrowserOrApp;
 
@@ -835,17 +835,17 @@ interface nsIDocShell : nsIDocShellTreeI
   readonly attribute DOMString appManifestURL;
 
   /**
    * Like nsIDocShellTreeItem::GetSameTypeParent, except this ignores <iframe
    * mozbrowser> and <iframe mozapp> boundaries.
    */
   nsIDocShell getSameTypeParentIgnoreBrowserAndAppBoundaries();
 
-  /** 
+  /**
    * True iff asynchronous panning and zooming is enabled for this
    * docshell.
    */
   readonly attribute bool asyncPanZoomEnabled;
 
   /**
    * The sandbox flags on the docshell. These reflect the value of the sandbox
    * attribute of the associated IFRAME or CSP-protectable content, if
@@ -1044,9 +1044,13 @@ interface nsIDocShell : nsIDocShellTreeI
   [noscript,notxpcom,nostdcall] void notifyJSRunToCompletionStop();
 
   /**
    * This attribute determines whether a document which is not about:blank has
    * already be loaded by this docShell.
    */
   [infallible] readonly attribute boolean hasLoadedNonBlankURI;
 
+  /**
+   * Holds the id of the payment request associated with this docshell if any.
+   */
+  attribute DOMString paymentRequestId;
 };
--- a/dom/payment/Payment.manifest
+++ b/dom/payment/Payment.manifest
@@ -1,6 +1,9 @@
 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 {82144756-72ab-45b7-8621-f3dad431dd2f} PaymentProvider.js
+contract @mozilla.org/payment/provider;1 {82144756-72ab-45b7-8621-f3dad431dd2f}
new file mode 100644
--- /dev/null
+++ b/dom/payment/PaymentProvider.js
@@ -0,0 +1,293 @@
+/* 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, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
+                                   "@mozilla.org/childprocessmessagemanager;1",
+                                   "nsIMessageSender");
+
+XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
+                                   "@mozilla.org/uuid-generator;1",
+                                   "nsIUUIDGenerator");
+
+
+const PREF_DEBUG = "dom.payment.debug";
+
+let _debug;
+try {
+  _debug = Services.prefs.getPrefType(PREF_DEBUG) == Ci.nsIPrefBranch.PREF_BOOL
+           && Services.prefs.getBoolPref(PREF_DEBUG);
+} catch(e) {
+  _debug = false;
+}
+
+function DEBUG(s) {
+  if (!_debug) {
+    return;
+  }
+  dump("== Payment Provider == " + s + "\n");
+}
+
+function DEBUG_E(s) {
+  dump("== Payment Provider ERROR == " + s + "\n");
+}
+
+const kPaymentFlowCancelled = "payment-flow-cancelled";
+
+function PaymentProvider() {
+}
+
+PaymentProvider.prototype = {
+  init: function(aWindow) {
+    let docshell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                          .getInterface(Ci.nsIWebNavigation)
+                          .QueryInterface(Ci.nsIDocShell);
+    this._requestId = docshell.paymentRequestId;
+    this._oncancelObserver = this.oncancel.bind(this);
+    Services.obs.addObserver(this._oncancelObserver,
+                             kPaymentFlowCancelled,
+                             false);
+    this._strategy = Cc["@mozilla.org/payment/provider-strategy;1"]
+                       .createInstance(Ci.nsIPaymentProviderStrategy);
+    this._window = aWindow;
+  },
+
+  paymentSuccess: function(aResult) {
+    _debug && DEBUG("paymentSuccess " + aResult);
+    let glue = Cc["@mozilla.org/payment/ui-glue;1"]
+                 .createInstance(Ci.nsIPaymentUIGlue);
+    glue.closePaymentFlow(this._requestId).then(() => {
+      if (!this._requestId) {
+        return;
+      }
+      cpmm.sendAsyncMessage("Payment:Success", { result: aResult,
+                                                 requestId: this._requestId });
+    });
+  },
+
+  paymentFailed: function(aError) {
+    _debug && DEBUG("paymentFailed " + aError);
+    let glue = Cc["@mozilla.org/payment/ui-glue;1"]
+                 .createInstance(Ci.nsIPaymentUIGlue);
+    glue.closePaymentFlow(this._requestId).then(() => {
+      if (!this._requestId) {
+        return;
+      }
+      cpmm.sendAsyncMessage("Payment:Failed", { errorMsg: aError,
+                                                requestId: this._requestId });
+    });
+
+  },
+
+  get paymentServiceId() {
+    return this._strategy.paymentServiceId;
+  },
+
+  set paymentServiceId(aServiceId) {
+    this._strategy.paymentServiceId = aServiceId;
+  },
+
+  /**
+   * We expose to the payment provider the information of all the SIMs
+   * available in the device. iccInfo is an object of this form:
+   * {
+   *   "serviceId1": {
+   *      mcc: <string>,
+   *      mnc: <string>,
+   *      iccId: <string>,
+   *      dataPrimary: <boolean>
+   *    },
+   *   "serviceIdN": {...}
+   * }
+   */
+  get iccInfo() {
+    return this._strategy.iccInfo;
+  },
+
+  oncancel: function() {
+    _debug && DEBUG("Cleaning up!");
+
+    this._strategy.cleanup();
+    Services.obs.removeObserver(this._oncancelObserver, kPaymentFlowCancelled);
+    if (this._cleanup) {
+      this._cleanup();
+    }
+  },
+
+  classID: Components.ID("{82144756-72ab-45b7-8621-f3dad431dd2f}"),
+
+  contractID: "@mozilla.org/payment/provider;1",
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
+                                         Ci.nsIObserver,
+                                         Ci.nsIDOMGlobalPropertyInitializer])
+};
+
+#if defined(MOZ_B2G_RIL) || defined(MOZ_WIDGET_ANDROID)
+
+XPCOMUtils.defineLazyServiceGetter(this, "smsService",
+                                   "@mozilla.org/sms/smsservice;1",
+                                   "nsISmsService");
+
+const kSilentSmsReceivedTopic = "silent-sms-received";
+
+// In order to send messages through nsISmsService, we need to implement
+// nsIMobileMessageCallback, as the WebSMS API implementation is not usable
+// from JS.
+function SilentSmsRequest(aDOMRequest) {
+  this.request = aDOMRequest;
+}
+
+SilentSmsRequest.prototype = {
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIMobileMessageCallback]),
+
+  classID: Components.ID("{1d58889c-5660-4cca-a8fd-97ef63e5d3b2}"),
+
+  notifyMessageSent: function notifyMessageSent(aMessage) {
+    _debug && DEBUG("Silent message successfully sent");
+    Services.DOMRequest.fireSuccessAsync(this.request, aMessage);
+  },
+
+  notifySendMessageFailed: function notifySendMessageFailed(aError) {
+    DEBUG_E("Error sending silent message " + aError);
+    Services.DOMRequest.fireErrorAsync(this.request, aError);
+  }
+};
+
+PaymentProvider.prototype._silentNumbers = null;
+
+PaymentProvider.prototype._silentSmsObservers = null;
+
+PaymentProvider.prototype.sendSilentSms = function(aNumber, aMessage) {
+  _debug && DEBUG("Sending silent message " + aNumber + " - " + aMessage);
+
+  let request = Services.DOMRequest.createRequest(this._window);
+
+  if (this._strategy.paymentServiceId === null) {
+    DEBUG_E("No payment service ID set. Cannot send silent SMS");
+    Services.DOMRequest.fireErrorAsync(request,
+                                       "NO_PAYMENT_SERVICE_ID");
+    return request;
+  }
+
+  let smsRequest = new SilentSmsRequest(request);
+  smsService.send(this._strategy.paymentServiceId, aNumber, aMessage, true,
+                  smsRequest);
+  return request;
+};
+
+PaymentProvider.prototype.observeSilentSms = function(aNumber, aCallback) {
+  _debug && DEBUG("observeSilentSms " + aNumber);
+
+  if (!this._silentSmsObservers) {
+    this._silentSmsObservers = {};
+    this._silentNumbers = [];
+    this._onSilentSmsObserver = this._onSilentSms.bind(this);
+    Services.obs.addObserver(this._onSilentSmsObserver,
+                             kSilentSmsReceivedTopic,
+                             false);
+  }
+
+  if (!this._silentSmsObservers[aNumber]) {
+    this._silentSmsObservers[aNumber] = [];
+    this._silentNumbers.push(aNumber);
+    smsService.addSilentNumber(aNumber);
+  }
+
+  if (this._silentSmsObservers[aNumber].indexOf(aCallback) == -1) {
+    this._silentSmsObservers[aNumber].push(aCallback);
+  }
+  return;
+};
+
+PaymentProvider.prototype.removeSilentSmsObserver = function(aNumber, aCallback) {
+  _debug && DEBUG("removeSilentSmsObserver " + aNumber);
+
+  if (!this._silentSmsObservers || !this._silentSmsObservers[aNumber]) {
+    _debug && DEBUG("No observers for " + aNumber);
+    return;
+  }
+
+  let index = this._silentSmsObservers[aNumber].indexOf(aCallback);
+  if (index != -1) {
+    this._silentSmsObservers[aNumber].splice(index, 1);
+    if (this._silentSmsObservers[aNumber].length == 0) {
+      this._silentSmsObservers[aNumber] = null;
+      this._silentNumbers.splice(this._silentNumbers.indexOf(aNumber), 1);
+      smsService.removeSilentNumber(aNumber);
+    }
+  } else if (_debug) {
+    DEBUG("No callback found for " + aNumber);
+  }
+  return;
+};
+
+PaymentProvider.prototype._onSilentSms = function(aSubject, aTopic, aData) {
+  _debug && DEBUG("Got silent message! " + aSubject.sender + " - " + aSubject.body);
+
+  let number = aSubject.sender;
+  if (!number || this._silentNumbers.indexOf(number) == -1) {
+    _debug && DEBUG("No observers for " + number);
+    return;
+  }
+
+  // If the service ID is null it means that the payment provider asked the
+  // user for her MSISDN, so we are in a MT only SMS auth flow. In this case
+  // we manually set the service ID to the one corresponding with the SIM
+  // that received the SMS.
+  if (this._strategy.paymentServiceId === null) {
+    let i = 0;
+    while(i < gRil.numRadioInterfaces) {
+      if (this.iccInfo[i].iccId === aSubject.iccId) {
+        this._strategy.paymentServiceId = i;
+        break;
+      }
+      i++;
+    }
+  }
+
+  this._silentSmsObservers[number].forEach(function(callback) {
+    callback(aSubject);
+  });
+};
+
+PaymentProvider.prototype._cleanup = function() {
+  if (!this._silentNumbers) {
+    return;
+  }
+
+  while (this._silentNumbers.length) {
+    let number = this._silentNumbers.pop();
+    smsService.removeSilentNumber(number);
+  }
+  this._silentNumbers = null;
+  this._silentSmsObservers = null;
+  Services.obs.removeObserver(this._onSilentSmsObserver,
+                              kSilentSmsReceivedTopic);
+};
+
+#else
+
+PaymentProvider.prototype.sendSilentSms = function(aNumber, aMessage) {
+  throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+};
+
+PaymentProvider.prototype.observeSilentSms = function(aNumber, aCallback) {
+  throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+};
+
+PaymentProvider.prototype.removeSilentSmsObserver = function(aNumber, aCallback) {
+  throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+};
+
+#endif
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PaymentProvider]);
new file mode 100644
--- /dev/null
+++ b/dom/payment/PaymentProviderUtils.cpp
@@ -0,0 +1,28 @@
+/* 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/. */
+
+#include "mozilla/dom/NavigatorBinding.h"
+#include "PaymentProviderUtils.h"
+#include "nsGlobalWindow.h"
+#include "nsJSUtils.h"
+#include "nsIDocShell.h"
+
+using namespace mozilla::dom;
+
+/* static */ bool
+PaymentProviderUtils::EnabledForScope(JSContext* aCx,
+                                      JSObject* aGlobal)
+{
+  nsCOMPtr<nsPIDOMWindow> win =
+    do_QueryInterface(nsJSUtils::GetStaticScriptGlobal(aGlobal));
+  NS_ENSURE_TRUE(win, false);
+
+  nsIDocShell *docShell = win->GetDocShell();
+  NS_ENSURE_TRUE(docShell, false);
+
+  nsString paymentRequestId;
+  docShell->GetPaymentRequestId(paymentRequestId);
+
+  return !paymentRequestId.IsEmpty();
+}
new file mode 100644
--- /dev/null
+++ b/dom/payment/PaymentProviderUtils.h
@@ -0,0 +1,25 @@
+/* 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/. */
+
+#ifndef mozilla_dom_payment_PaymentProviderEnabler_h
+#define mozilla_dom_payment_PaymentProviderEnabler_h
+
+#include "jsapi.h"
+
+struct JSContext;
+class JSObject;
+
+namespace mozilla {
+namespace dom {
+  
+class PaymentProviderUtils
+{
+public:
+  static bool EnabledForScope(JSContext*, JSObject*);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_payment_PaymentProviderEnabler_h
--- a/dom/payment/interfaces/moz.build
+++ b/dom/payment/interfaces/moz.build
@@ -2,13 +2,14 @@
 # vim: set filetype=python:
 # 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/.
 
 XPIDL_SOURCES += [
     'nsINavigatorPayment.idl',
     'nsIPaymentFlowInfo.idl',
+    'nsIPaymentProviderStrategy.idl',
     'nsIPaymentUIGlue.idl',
 ]
 
 XPIDL_MODULE = 'dom_payment'
 
new file mode 100644
--- /dev/null
+++ b/dom/payment/interfaces/nsIPaymentProviderStrategy.idl
@@ -0,0 +1,14 @@
+/* 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/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(c3971bd9-0fbf-48d3-9498-0ac340d0d216)]
+interface nsIPaymentProviderStrategy : nsISupports
+{
+  attribute DOMString paymentServiceId;
+  readonly attribute jsval iccInfo;
+
+  void cleanup();
+};
--- a/dom/payment/interfaces/nsIPaymentUIGlue.idl
+++ b/dom/payment/interfaces/nsIPaymentUIGlue.idl
@@ -7,24 +7,26 @@
 interface nsIPaymentFlowInfo;
 
 [scriptable, function, uuid(b9afa678-71a5-4975-bcdb-0c4098730eff)]
 interface nsIPaymentUIGlueCallback : nsISupports
 {
     void onresult(in DOMString requestId, in DOMString result);
 };
 
-[scriptable, uuid(4dda9aa0-df88-4dcd-a583-199e516fa438)]
+[scriptable, uuid(4dc09e33-d395-4e1d-acb4-e85415181270)]
 interface nsIPaymentUIGlue : nsISupports
 {
     // The 'paymentRequestsInfo' contains the payment request information
     // for each JWT provided via navigator.mozPay call.
     void confirmPaymentRequest(in DOMString requestId,
                                in jsval paymentRequestsInfo,
                                in nsIPaymentUIGlueCallback successCb,
                                in nsIPaymentUIGlueCallback errorCb);
 
     void showPaymentFlow(in DOMString requestId,
                          in nsIPaymentFlowInfo paymentFlowInfo,
                          in nsIPaymentUIGlueCallback errorCb);
 
-    void cleanup();
+    // The promise resolves with no value as soon as the payment window is
+    // closed.
+    jsval /*Promise*/ closePaymentFlow(in DOMString requestId);
 };
--- a/dom/payment/moz.build
+++ b/dom/payment/moz.build
@@ -1,19 +1,39 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 DIRS += ['interfaces']
 
+EXPORTS.mozilla.dom += [
+    'PaymentProviderUtils.h',
+]
+
+SOURCES += [
+    'PaymentProviderUtils.cpp',
+]
+
 EXTRA_PP_JS_MODULES += [
     'Payment.jsm',
 ]
 
 EXTRA_COMPONENTS += [
     'Payment.js',
     'Payment.manifest',
-    'PaymentFlowInfo.js',
+    'PaymentFlowInfo.js'
+]
+
+EXTRA_PP_COMPONENTS += [
+    'PaymentProvider.js'
+]
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+    '/dom/base'
 ]
 
 XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
+
+MOCHITEST_MANIFESTS += ['tests/mochitest/mochitest.ini']
new file mode 100644
--- /dev/null
+++ b/dom/payment/tests/mochitest/file_mozpayproviderchecker.html
@@ -0,0 +1,28 @@
+<!--
+  * 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/. */
+ -->
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test app for bug 1097928 - Check if MozPaymentProvider API is exposed</title>
+</head>
+<body>
+<div id='test'>
+<script type="application/javascript;version=1.8">
+  function receiveMessage(event) {
+    let message = JSON.parse(event.data);
+    let exposed = (navigator.mozPaymentProvider != undefined);
+    window.parent.postMessage(JSON.stringify({
+      iframeType: message.iframeType,
+      exposed: exposed
+    }), "*");
+  }
+
+  window.addEventListener("message", receiveMessage, false, true);
+</script>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/payment/tests/mochitest/file_payproviderfailure.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset="utf-8">
+  </head>
+  <body>
+    <script>
+    navigator.mozPaymentProvider.paymentFailed();
+    </script>
+  </body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/payment/tests/mochitest/file_payprovidersuccess.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset="utf-8">
+  </head>
+  <body>
+    <script>
+    navigator.mozPaymentProvider.paymentSuccess("aResult");
+    </script>
+  </body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/payment/tests/mochitest/mochitest.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+skip-if=true # This test uses MockPaymentsUIGlue which uses __exposedProps__
+             # we need to move this to a chrome test, but these are not
+             # available in b2g or android for now.
+
+support-files=
+  file_mozpayproviderchecker.html
+  file_payprovidersuccess.html
+  file_payproviderfailure.html
+
+[test_mozpaymentprovider.html]
+[test_mozpay_callbacks.html]
new file mode 100644
--- /dev/null
+++ b/dom/payment/tests/mochitest/test_mozpay_callbacks.html
@@ -0,0 +1,108 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+  https://bugzilla.mozilla.org/show_bug.cgi?id=1097928
+-->
+<html>
+<head>
+  <title>Test for navigator.mozPay. Bug 1097928</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript;version=1.8">
+
+SimpleTest.waitForExplicitFinish();
+
+let tests = [() => {
+  /*
+  {
+   "iss": "323d34dc-b5cf-4822-8e47-6a4515dc74db",
+    "typ": "mozilla/payments/test/success",
+    "request": {
+        "name": "Virtual Kiwi",
+        "id": "af1f960a-3f90-4e2d-a20f-d5170aee49f2",
+        "postbackURL": "https://inapp-pay-test.paas.allizom.org/mozpay",
+        "productData": "localTransID=14546cd1-db9b-4759-986f-2a6a295fdcc1",
+        "chargebackURL": "https://inapp-pay-test.paas.allizom.org/mozpay",
+        "description": "The forbidden fruit"
+    }
+  }
+  */
+  let jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiIzMjNkMzRkYy1iNWNmLTQ4MjItOGU0Ny02YTQ1MTVkYzc0ZGIiLCJyZXF1ZXN0Ijp7ImRlc2NyaXB0aW9uIjoiVGhlIGZvcmJpZGRlbiBmcnVpdCIsImlkIjoiYWYxZjk2MGEtM2Y5MC00ZTJkLWEyMGYtZDUxNzBhZWU0OWYyIiwicG9zdGJhY2tVUkwiOiJodHRwczovL2luYXBwLXBheS10ZXN0LnBhYXMuYWxsaXpvbS5vcmcvbW96cGF5L3Bvc3RiYWNrIiwicHJvZHVjdERhdGEiOiJsb2NhbFRyYW5zSUQ9MTQ1NDZjZDEtZGI5Yi00NzU5LTk4NmYtMmE2YTI5NWZkY2MxIiwiY2hhcmdlYmFja1VSTCI6Imh0dHBzOi8vaW5hcHAtcGF5LXRlc3QucGFhcy5hbGxpem9tLm9yZy9tb3pwYXkvY2hhcmdlYmFjayIsIm5hbWUiOiJWaXJ0dWFsIEtpd2kifSwidHlwIjoibW96aWxsYS9wYXltZW50cy90ZXN0L3N1Y2Nlc3MifQ.8zaeYFUCwKkZWk2TFf2wEJWrmiSYQGNbpKc2ADkvL9s";
+  let req = window.navigator.mozPay(jwt);
+  req.onsuccess = (result) => {
+    ok(true, "Expected mozPay success");
+    runTest();
+  };
+  req.onerror = (error) => {
+    ok(false, "Unexpected mozPay error " + error);
+    SimpleTest.finish();
+  };
+}, () => {
+  /*
+  {
+      "iss": "323d34dc-b5cf-4822-8e47-6a4515dc74db",
+      "typ": "mozilla/payments/test/failure",
+      "request": {
+          "name": "Virtual Kiwi",
+          "id": "af1f960a-3f90-4e2d-a20f-d5170aee49f2",
+          "postbackURL": "https://inapp-pay-test.paas.allizom.org/mozpay",
+          "productData": "localTransID=cc3c0994-33e8-4a21-aa2c-75ee44f5fe75",
+          "chargebackURL": "https://inapp-pay-test.paas.allizom.org/mozpay",
+          "description": "The forbidden fruit"
+      }
+  }
+   */
+  let jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiIzMjNkMzRkYy1iNWNmLTQ4MjItOGU0Ny02YTQ1MTVkYzc0ZGIiLCJyZXF1ZXN0Ijp7ImRlc2NyaXB0aW9uIjoiVGhlIGZvcmJpZGRlbiBmcnVpdCIsImlkIjoiYWYxZjk2MGEtM2Y5MC00ZTJkLWEyMGYtZDUxNzBhZWU0OWYyIiwicG9zdGJhY2tVUkwiOiJodHRwczovL2luYXBwLXBheS10ZXN0LnBhYXMuYWxsaXpvbS5vcmcvbW96cGF5L3Bvc3RiYWNrIiwicHJvZHVjdERhdGEiOiJsb2NhbFRyYW5zSUQ9Y2MzYzA5OTQtMzNlOC00YTIxLWFhMmMtNzVlZTQ0ZjVmZTc1IiwiY2hhcmdlYmFja1VSTCI6Imh0dHBzOi8vaW5hcHAtcGF5LXRlc3QucGFhcy5hbGxpem9tLm9yZy9tb3pwYXkvY2hhcmdlYmFjayIsIm5hbWUiOiJWaXJ0dWFsIEtpd2kifSwidHlwIjoibW96aWxsYS9wYXltZW50cy90ZXN0L2ZhaWx1cmUifQ.1uV4-HkmwO0oDv50wi1Ma4tNpnxoFGaw5zaPj8xkcAc";
+  let req = window.navigator.mozPay(jwt);
+  req.onsuccess = (result) => {
+    ok(false, "Unexpected mozPay success " + result);
+    SimpleTest.finish();
+  };
+  req.onerror = (error) => {
+    ok(true, "Expected mozPay error");
+    runTest();
+  };
+}];
+
+function runTest() {
+  if (!tests.length) {
+    ok(true, "Done!");
+    SimpleTest.finish();
+    return;
+  }
+  tests.shift()();
+}
+
+SpecialPowers.MockPaymentsUIGlue.init(window);
+
+SpecialPowers.pushPrefEnv({
+  "set": [
+    ["dom.payment.skipHTTPSCheck", "true"],
+    ["dom.payment.debug", "true"],
+    ["dom.payment.provider.1.name", "SuccessProvider"],
+    ["dom.payment.provider.1.description", ""],
+    ["dom.payment.provider.1.uri",
+     "http://mochi.test:8888/tests/dom/payment/tests/mochitest/file_payprovidersuccess.html?req="],
+    ["dom.payment.provider.1.type", "mozilla/payments/test/success"],
+    ["dom.payment.provider.1.requestMethod", "GET"],
+    ["dom.payment.provider.2.name", "FailureProvider"],
+    ["dom.payment.provider.2.description", ""],
+    ["dom.payment.provider.2.uri",
+     "http://mochi.test:8888/tests/dom/payment/tests/mochitest/file_payproviderfailure.html?req="],
+    ["dom.payment.provider.2.type", "mozilla/payments/test/failure"],
+    ["dom.payment.provider.2.requestMethod", "GET"],
+  ]
+  }, () => {
+    runTest();
+  });
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/payment/tests/mochitest/test_mozpaymentprovider.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+  https://bugzilla.mozilla.org/show_bug.cgi?id=1097928
+-->
+<html>
+<head>
+  <title>Test for navigator.mozPaymentProvider exposure. Bug 1097928</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript;version=1.8">
+
+SimpleTest.waitForExplicitFinish();
+
+// We create two iframes. The first one is a regular iframe with no payment
+// information and so it should not have access to the MozPaymentProvider
+// API. For the second iframe we set a dummy payment request ID which should
+// expose the MozPaymentProvider API.
+let tests = [function() {
+  // Iframe with no payment information.
+  let iframe = document.createElement("iframe");
+  iframe.setAttribute("mozbrowser", "true");
+  iframe.src = "file_mozpayproviderchecker.html";
+
+  document.getElementById("content").appendChild(iframe);
+
+  iframe.addEventListener("load", function onLoad() {
+    iframe.removeEventListener("load", onLoad);
+    iframe.contentWindow.postMessage(JSON.stringify({
+      iframeType: "regular"
+    }), "*");
+  }, false);
+}, function() {
+  // Payment iframe.
+  let paymentIframe = document.createElement("iframe");
+  paymentIframe.setAttribute("mozbrowser", "true");
+  paymentIframe.src = "file_mozpayproviderchecker.html";
+
+  document.getElementById("content").appendChild(paymentIframe);
+
+  let Ci = SpecialPowers.Ci;
+  let docshell = SpecialPowers.wrap(paymentIframe.contentWindow)
+                              .QueryInterface(Ci.nsIInterfaceRequestor)
+                              .getInterface(Ci.nsIWebNavigation)
+                              .QueryInterface(Ci.nsIDocShell);
+  docshell.paymentRequestId = "dummyid";
+
+  paymentIframe.addEventListener("load", function onLoad() {
+    paymentIframe.removeEventListener("load", onLoad);
+    paymentIframe.contentWindow.postMessage(JSON.stringify({
+      iframeType: "payment"
+    }), "*");
+  }, false);
+}];
+
+function receiveMessage(event) {
+  let message = JSON.parse(event.data);
+  switch (message.iframeType) {
+    case "regular":
+      ok(!message.exposed, "MozPaymentProvider is not exposed in regular iframe");
+      break;
+    case "payment":
+      ok(message.exposed, "MozPaymentProvider is exposed in payment iframe");
+      break;
+    default:
+      ok(false, "Unexpected iframe type");
+      SimpleTest.finish();
+  }
+  runTest();
+}
+
+function runTest() {
+  if (!tests.length) {
+    ok(true, "Done!");
+    SimpleTest.finish();
+    return;
+  }
+  tests.shift()();
+}
+
+window.addEventListener("message", receiveMessage, false, true);
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/webidl/MozPaymentProvider.webidl
@@ -0,0 +1,33 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ */
+
+callback SilentSmsCallback = void (optional MozSmsMessage message);
+
+dictionary PaymentIccInfo {
+  DOMString mcc;
+  DOMString mnc;
+  DOMString iccId;
+  boolean dataPrimary;
+};
+
+[NavigatorProperty="mozPaymentProvider",
+ NoInterfaceObject,
+ HeaderFile="mozilla/dom/PaymentProviderUtils.h",
+ Func="mozilla::dom::PaymentProviderUtils::EnabledForScope",
+ JSImplementation="@mozilla.org/payment/provider;1"]
+interface PaymentProvider {
+  readonly attribute DOMString? paymentServiceId;
+  // We expose to the payment provider the information of all the SIMs
+  // available in the device.
+  [Cached, Pure] readonly attribute sequence<PaymentIccInfo>? iccInfo;
+
+  void paymentSuccess(optional DOMString result);
+  void paymentFailed(optional DOMString error);
+
+  DOMRequest sendSilentSms(DOMString number, DOMString message);
+  void observeSilentSms(DOMString number, SilentSmsCallback callback);
+  void removeSilentSmsObserver(DOMString number, SilentSmsCallback callback);
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -804,8 +804,13 @@ if CONFIG['MOZ_EME']:
     WEBIDL_FILES += [
         'MediaEncryptedEvent.webidl',
         'MediaKeyError.webidl',
         'MediaKeyMessageEvent.webidl',
         'MediaKeys.webidl',
         'MediaKeySession.webidl',
         'MediaKeySystemAccess.webidl',
     ]
+
+if CONFIG['MOZ_PAY']:
+    WEBIDL_FILES += [
+        'MozPaymentProvider.webidl'
+    ]
--- a/testing/marionette/jar.mn
+++ b/testing/marionette/jar.mn
@@ -27,9 +27,10 @@ marionette.jar:
   content/SpecialPowersObserverAPI.js (../specialpowers/content/SpecialPowersObserverAPI.js)
   content/ChromePowers.js (../mochitest/tests/SimpleTest/ChromePowers.js)
   content/MozillaLogger.js (../specialpowers/content/MozillaLogger.js)
 
 % resource specialpowers %modules/
   modules/MockFilePicker.jsm (../specialpowers/content/MockFilePicker.jsm)
   modules/MockColorPicker.jsm (../specialpowers/content/MockColorPicker.jsm)
   modules/MockPermissionPrompt.jsm (../specialpowers/content/MockPermissionPrompt.jsm)
+  modules/MockPaymentsUIGlue.jsm (../specialpowers/content/MockPaymentsUIGlue.jsm)
   modules/Assert.jsm (../modules/Assert.jsm)
new file mode 100644
--- /dev/null
+++ b/testing/specialpowers/content/MockPaymentsUIGlue.jsm
@@ -0,0 +1,110 @@
+/* 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/. */
+
+this.EXPORTED_SYMBOLS = ["MockPaymentsUIGlue"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cm = Components.manager;
+const Cu = Components.utils;
+
+const CONTRACT_ID = "@mozilla.org/payment/ui-glue;1";
+
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+let classID;
+let oldFactory;
+let newFactory = function(window) {
+  return {
+    createInstance: function(aOuter, aIID) {
+      if (aOuter) {
+        throw Components.results.NS_ERROR_NO_AGGREGATION;
+      }
+      return new MockPaymentsUIGlueInstance(window).QueryInterface(aIID);
+    },
+    lockFactory: function(aLock) {
+      throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+    },
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory])
+  };
+};
+
+this.MockPaymentsUIGlue = {
+  init: function(aWindow) {
+    try {
+      classID = registrar.contractIDToCID(CONTRACT_ID);
+      oldFactory = Cm.getClassObject(Cc[CONTRACT_ID], Ci.nsIFactory);
+    } catch (ex) {
+      oldClassID = "";
+      oldFactory = null;
+      dump("TEST-INFO | can't get payments ui glue registered component, " +
+          "assuming there is none");
+    }
+    if (oldFactory) {
+      registrar.unregisterFactory(classID, oldFactory);
+    }
+    registrar.registerFactory(classID, "", CONTRACT_ID,
+                              new newFactory(aWindow));
+  },
+
+  reset: function() {
+  },
+
+  cleanup: function() {
+    this.reset();
+    if (oldFactory) {
+      registrar.unregisterFactory(classID, newFactory);
+      registrar.registerFactory(classID, "", CONTRACT_ID, oldFactory);
+    }
+  }
+};
+
+function MockPaymentsUIGlueInstance(aWindow) {
+  this.window = aWindow;
+};
+
+MockPaymentsUIGlueInstance.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPaymentUIGlue]),
+
+  confirmPaymentRequest: function(aRequestId,
+                                  aRequests,
+                                  aSuccessCb,
+                                  aErrorCb) {
+    aSuccessCb.onresult(aRequestId, aRequests[0].type);
+  },
+
+  showPaymentFlow: function(aRequestId,
+                            aPaymentFlowInfo,
+                            aErrorCb) {
+    let document = this.window.document;
+    let frame = document.createElement("iframe");
+    frame.setAttribute("mozbrowser", true);
+    frame.setAttribute("remote", true);
+    document.body.appendChild(frame);
+    let docshell = frame.contentWindow
+                        .QueryInterface(Ci.nsIInterfaceRequestor)
+                        .getInterface(Ci.nsIWebNavigation)
+                        .QueryInterface(Ci.nsIDocShell);
+    docshell.paymentRequestId = aRequestId;
+    frame.src = aPaymentFlowInfo.uri + aPaymentFlowInfo.jwt;
+  },
+
+  closePaymentFlow: function(aRequestId) {
+    return Promise.resolve();
+  }
+};
+// Expose everything to content. We call reset() here so that all of the relevant
+// lazy expandos get added.
+MockPaymentsUIGlue.reset();
+function exposeAll(obj) {
+  var props = {};
+  for (var prop in obj)
+    props[prop] = 'rw';
+  obj.__exposedProps__ = props;
+}
+exposeAll(MockPaymentsUIGlue);
+exposeAll(MockPaymentsUIGlueInstance.prototype);
--- a/testing/specialpowers/content/specialpowersAPI.js
+++ b/testing/specialpowers/content/specialpowersAPI.js
@@ -9,16 +9,17 @@
 
 var Ci = Components.interfaces;
 var Cc = Components.classes;
 var Cu = Components.utils;
 
 Cu.import("resource://specialpowers/MockFilePicker.jsm");
 Cu.import("resource://specialpowers/MockColorPicker.jsm");
 Cu.import("resource://specialpowers/MockPermissionPrompt.jsm");
+Cu.import("resource://specialpowers/MockPaymentsUIGlue.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 Cu.importGlobalProperties(["File"]);
 
 // Allow stuff from this scope to be accessed from non-privileged scopes. This
 // would crash if used outside of automation.
@@ -516,25 +517,29 @@ SpecialPowersAPI.prototype = {
    * function strips any wrappers if they exist and compare the underlying
    * values.
    */
   compare: function(a, b) {
     return unwrapIfWrapped(a) === unwrapIfWrapped(b);
   },
 
   get MockFilePicker() {
-    return MockFilePicker
+    return MockFilePicker;
   },
 
   get MockColorPicker() {
-    return MockColorPicker
+    return MockColorPicker;
   },
 
   get MockPermissionPrompt() {
-    return MockPermissionPrompt
+    return MockPermissionPrompt;
+  },
+
+  get MockPaymentsUIGlue() {
+    return MockPaymentsUIGlue;
   },
 
   loadChromeScript: function (url) {
     // Create a unique id for this chrome script
     let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"]
                           .getService(Ci.nsIUUIDGenerator);
     let id = uuidGenerator.generateUUID().toString();
 
--- a/testing/specialpowers/jar.mn
+++ b/testing/specialpowers/jar.mn
@@ -4,13 +4,14 @@ specialpowers.jar:
   content/specialpowersAPI.js (content/specialpowersAPI.js)
   content/SpecialPowersObserverAPI.js (content/SpecialPowersObserverAPI.js)
   content/MozillaLogger.js (content/MozillaLogger.js)
 
 % resource specialpowers %modules/
   modules/MockFilePicker.jsm (content/MockFilePicker.jsm)
   modules/MockColorPicker.jsm (content/MockColorPicker.jsm)
   modules/MockPermissionPrompt.jsm (content/MockPermissionPrompt.jsm)
+  modules/MockPaymentsUIGlue.jsm (content/MockPaymentsUIGlue.jsm)
   modules/Assert.jsm (../modules/Assert.jsm)
 
 % component {59a52458-13e0-4d93-9d85-a637344f29a1} components/SpecialPowersObserver.js
 % contract @mozilla.org/special-powers-observer;1 {59a52458-13e0-4d93-9d85-a637344f29a1}
 % category profile-after-change @mozilla.org/special-powers-observer;1 @mozilla.org/special-powers-observer;1