WebAPI/WebPayment: Difference between revisions

From MozillaWiki
Jump to navigation Jump to search
(I brought the docs up to date with what Mozilla will be shipping in Firefox OS. Some major API changes: prices became pricePoint; postback/chargeback URLs are mandatory; and more!)
Line 1: Line 1:
= WebPayment API Specification (DRAFT)=
= WebPayment API Specification (DRAFT)=
== Goal ==
== Goal ==
The goal of this API is to allow Open Web Apps to initiative a payment (or a refund) form the user for a virtual good.
The goal of this API is to enable web content to collect payment (or issue a refund) for a virtual good via the <code>navigator.mozPay()</code> function.


== Status ==
== Status ==
See {{bug|767818}} for the 'navigator.mozPay' implementation.
See {{bug|767818}} for the <code>navigator.mozPay()</code> implementation.


Currently only implemented for B2G.
Currently only implemented for B2G.
Line 18: Line 18:


== Proposers ==
== Proposers ==
Andreas Gal and Fernando Jiménez. Based on Mike Hanson's and Kumar McMillan's work for the [https://developer.mozilla.org/en/Apps/In-app_payments Marketplace in-app payments] solution
Andreas Gal and Fernando Jiménez. Based on Mike Hanson's and Kumar McMillan's work for [https://developer.mozilla.org/en-US/docs/Apps/Publishing/In-app_payments Firefox Marketplace in-app payments].


= WebPayments Architecture (DRAFT) =
= WebPayments Architecture (DRAFT) =
== Introduction ==
== Introduction ==
The goal of this Web Payments architecture is to allow Open Web Apps to initiate a payment (or a refund) from the user for a virtual good via the <code>navigator.mozPay</code> function.
An open web app will interact with a [https://wiki.mozilla.org/WebAPI/WebPaymentProvider Payment Provider] via <code>navigator.mozPay()</code> and receive notifications POSTed to its server about the result of each payment. Users will see a payment flow hosted by the Payment Provider in a special window on the device.
 
An OWA should be able to access the in-app web payments services using an API (<code>navigator.mozPay</code>) exposed by the User Agent on the device (for the B2G use case). The UA must use an asynchronous message loop to convey billing requests and responses between the application and the Payment Processor (or Provider). In practice, the applications should never directly communicate with the Payment Processors's servers. Instead, the application should send billing requests to the UA via 'navigator.mozPay()' and should receive purchase responses from the UA in the form of asynchronous callbacks.


== Payment flow overview ==
== Payment flow overview ==
* The app initiates a payment by signing a JWT request and calling <code>navigator.mozPay()</code>.
* The app initiates a payment by signing a [http://openid.net/specs/draft-jones-json-web-token-07.html JSON Web Token (JWT)] request and calling <code>navigator.mozPay()</code>.
* This starts the buyflow in a content iframe inside a trusted dialog ("chrome dialog").
* This starts the buyflow in a content iframe inside a trusted dialog ("chrome dialog").
* A purchasing flow is served from the Payment Provider's server as an HTML5 document inside the trusted dialog.
* A purchasing flow is served from the [https://wiki.mozilla.org/WebAPI/WebPaymentProvider Payment Provider]'s server as an HTML5 document inside the trusted dialog.
* The buyer is authenticated by the Payment Provider (via the network (radio), BrowserID assertion or whatever authentication mechanisms the Payment Provider chooses).
* The buyer is authenticated by the [https://wiki.mozilla.org/WebAPI/WebPaymentProvider Payment Provider] (via the network (radio), Mozilla Persona assertion or whatever authentication mechanisms the Payment Provider chooses).
* The buyer completes or cancels the purchase. (Note that the Payment Provider might also require an authorization step).
* The buyer completes or cancels the purchase.
* The app receives a Javascript callback when the buyer accepts or cancels the purchase.
* The app receives a Javascript callback when the buyer completes or cancels the purchase.
* The app serser receives a signed POST request with a Payment Provider transaction identifier indicating that the purchase was completed successfully.
* The app server receives a signed POST request with a Payment Provider transaction identifier indicating that the purchase was completed successfully (or it failed).


== Detailed payment flow ==
== Detailed payment flow ==
<span style="color:#009000">'''This detailed payment flow is based on the proposal for supporting Telefónica's Carrier Billing functionality in B2G, but it could be extended to any other Payment Provider.'''</span>
<span style="color:#009000">'''This detailed payment flow is based on Mozilla's [https://developer.mozilla.org/en-US/docs/Apps/Publishing/In-app_payments initial implementation for Firefox OS] but it could be extended to any other Payment Provider.'''</span>


=== Entities ===
=== Definitions ===
; Application: Open Web App which offers digital goods to be sold. OWAs that charge users via <code>navigator.mozPay()</code> require a client and server side.
; Application: Open Web App which offers digital goods to be sold. OWAs charge users via <code>navigator.mozPay()</code> and require a client and server.
; BlueVia portal: BlueVia is Telefónica's developer program. Developers can register in BlueVia portal in order to make use of TEF Carrier APIs, such as the payment API. We will be using Telefónica's Payment Provider as an example for the payment flow explanation and BlueVia is part of it.
; Application Key: A public identifier that can be transmitted in a JSON object so that a Payment Provider can identify the Application.
; Application Secret: A private string shared between Developer and Payment Provider. This will be used to sign [http://openid.net/specs/draft-jones-json-web-token-07.html JSON Web Tokens (JWTs)] and must be securely protected on a web server.
; Developer: Application developer, seller of digital goods.
; Developer: Application developer, seller of digital goods.
; Mozilla Marketplace:Developer portal and application repository. Developers can submit apps to the Mozilla Marketplace so the users can purchase and download this apps. The Mozilla Marketplace would also be using the <code>navigator.mozPay</code> function to charge users for application purchases.
; Firefox Marketplace: Developer portal and application repository. Developers can submit apps to the [https://marketplace.firefox.com/ Firefox Marketplace] so users can purchase and download the apps. The Firefox Marketplace uses the <code>navigator.mozPay</code> function to charge users for application purchases.
; Payment Provider (or Processor): Offers merchants online services for accepting electronic payments by a variety of payment methods (credict card (CC), direct to bill (D2B), etc.).
; Payment Provider: A client/server web application that serves content in a special iframe controlled by <code>navigator.mozPay()</code>. This conforms to the [https://wiki.mozilla.org/WebAPI/WebPaymentProvider Payment Provider] spec. The provider accepts payment from a user and disperses income to the Developer.
; Telefónica ID (pending branding): Telefónica's End user/Customer portal. This customers will be the ones charged for application downloads or in-app purchases.
; User: End user who wants to purchase a digital good.
; User: End user who wants to make a purchase for a digital good.
; User Agent: B2G (Firefox OS)
; User Agent: B2G


=== User sign-up and sign-in ===
=== User sign-in ===
'''In order to allow billing charges when the user can't be identified via the MSISDN transmitted over the network, the user should be registered and logged in Telefónica ID (Telefónica's customers portal).'''
This is an implementation detail of the [https://wiki.mozilla.org/WebAPI/WebPaymentProvider Payment Provider]. The <code>navigator.mozPay()</code> API does not prescribe any user authorization scheme.  


Currently, there is not a final decision about the authentication mechanisms chosen for B2G v1.
In [https://github.com/mozilla/webpay Mozilla's Payment Provider] implementation, users will sign-in via [https://login.persona.org/ Persona].
 
User sign-up and sign-in in Telefónica ID Connect is beyond the scope of this document.


=== Developer registration ===
=== Developer registration ===
'''In order to submit applications that makes use of the Telefónica Carrier Billing functionality to the Mozilla Marketplace an app developer should be registered in BlueVia (Telefónica's developers portal).'''
This is an implementation detail of the [https://wiki.mozilla.org/WebAPI/WebPaymentProvider Payment Provider]. The <code>navigator.mozPay()</code> API facilities two parties -- a Developer and a Payment Provider -- in making a transaction but it does not facilitate any part of the registration process.


For v1, B2G applications are going to be purchased via Mozilla Marketplace. So application developers that requires BlueVia in-app billing functionality should also be registeredd in BlueVia as part of his Mozilla Marketplace registration.
A developer who creates a JSON Web Token must do so with an Application Secret obtained from the Payment Provider. In [https://developer.mozilla.org/en-US/docs/Apps/Publishing/In-app_payments Mozilla's implementation], a developer signs up through the [https://marketplace.firefox.com/developers/ Firefox Marketplace Developer Hub], enters bank account details for payouts, obtains an Application Key and Application Secret, and can begin signing JWTs for purchase.


A privileged developer registration API is exposed by BlueVia.
=== Initiating a Payment ===
* The user opens an Application
* The user finds something interesting to purchase. Let's say it's a game and the user wants to purchase a Magical Unicorn to excel in the game.
* The user clicks a Buy Unicorn button.
* The Application server responds by first signing a [http://openid.net/specs/draft-jones-json-web-token-07.html JSON Web Token (JWT)] using its Application Secret. The JWT is a base64-encoded signed JSON object. The JSON contains all the details about the product such as name, description, price, etc.
* The Application bubbles up the JWT to the client and calls <code>navigator.mozPay([theJWT])</code>. This begins the hosted buy flow within a special window on the User Agent.


=== Application registration ===
Example of server-side JWT generation in Python using [https://pypi.python.org/pypi/PyJWT-mozilla PyJWT-mozilla]:
'''In order to provide in-app billing functionality an application should be registered in BlueVia'''
This is a similar case as developer registration. Open Web Apps that requires BlueVia in-app billing capabilities should be registered via Mozilla Marketplace (or potentialy other supported marketplace) and also automatically be registered in BlueVia.
 
An Application Key and Application Secret (generated by BlueVia) should be assigned and provided to the application developer. The application developer can provide BlueVia (via Mozilla Marketplace app registration) with <code>postback</code> and <code>chargeback</code> URLs (this URLs should be editable via Mozilla Marketplace / BlueVia portal).
 
The application secret will be used by the developer to sign the JWT included with the navigator.mozPay() function. The developer must save the application key and application secret securely in his app server. He must generate the signed JWT using server-side code.
 
A privileged app registration/edition API is exposed by BlueVia.
[[File:WebPaymentAppRegistrationFlow.png|600px|thumb|center|Application registration]]
 
=== In-app payment ===
* The user installs a previously registered application via Mozilla Marketplace.
* As his daily use the user decides to purchase a digital good offered by the application.
* The app generates a payment request that contains all the information about the item being purchased: price, currency, name, description, and so on. The app signs the payment request with its app secret and encodes the whole thing as a [http://openid.net/specs/draft-jones-json-web-token-07.html  JSON Web Token (JWT)]. The developer must generate the signed JWT using server-side code. <span style="color:#800000">The Mozilla Marketplace '''might''' expose a JWT generation tool based on the payment information provided by the developer and the supported payment methods that the developer should have previously set up. (TBD)</span>. In any case, there are several libraries (like [https://github.com/progrium/pyjwt PyJWT], [https://github.com/progrium/ruby-jwt ruby-jwt], [https://github.com/hokaccha/node-jwt-simple node-jwt-simple], [https://github.com/luciferous/jwt PHP luciferous/jwt], [http://code.google.com/p/jsontoken/ Java jsontoken] and [http://json.codeplex.com/ JSON.NET]) to help encoding and decoding JWT.
Example of server-side JWT generation:
<code>
<code>
   cakeToken = jwt.encode({
   paymentJWT = jwt.encode({
     "iss" : sellerIdentifier,
     "iss": APPLICATION_KEY,
     "aud" : "tu.com",
     "aud": "marketplace.firefox.com",
     "typ" : "tu.com/payments/v1/pay",
     "typ": "mozilla/payments/pay/v1",
     "exp" : 1337357297,
     "iat": 1337357297,
     "iat" : 1337360897,
     "exp": 1337360897,
     "request" : {
     "request": {
      "name" : "Piece of Cake",
       "id": "915c07fc-87df-46e5-9513-45cb6e504e39",
      "description" : "Virtual chocolate cake to fill your virtual tummy",
       "pricePoint": 1,
       "id": "unique-id-for-product",
       "name": "Magical Unicorn",
       "price" : [
       "description": "Adventure Game item",
      {
       "productData": "user_id=1234&my_session_id=XYZ",
        "currency": "USD",
       "postbackURL": "https://yourapp.com/payments/postback",
        "amount": "5.50"
       "chargebackURL": "https://yourapp.com/payments/chargeback"
      },  
       {
        "currency": "BRL",
        "amount": "11.07"
      }],
       "defaultPrice": "USD",
       "productData" : "my_product_id=1234&my_session_id=XYZ",
       "postbackURL" : "http://developersserver.com/postback",
       "chargebackURL" : "http://developerserver.com/chargeback"
     }
     }
   }, APP_SECRET)
   }, APPLICATION_SECRET)
</code>
</code>


Here is a detailed explanation of the in-app payment JWT:
Here is a detailed explanation of the payment JWT:
**'''iss''': The issuer of the JWT. This is the application key assigned during the app registration process for this specific payment provider (BlueVia).
**'''iss''' (mandatory): The issuer of the JWT. This is the Application Key assigned during the app registration process for this specific payment provider (e.g. Firefox Marketplace).
**'''typ''' (mandatory): The JWT type (identifies the payment provider, BlueVia, and the JWT version that must be supported by the provider).
**'''typ''' (mandatory): The JWT type. This identifies the payment provider, e.g. Firefox Marketplace, and the JWT version that must be supported by the provider. On the User Agent, typ is used to look up white-listed Payment Providers.
**'''iat''': Issued at time. This is a UTC Unix timestamp of when the JWT was issued.
**'''iat''': (mandatory) Issued at time. This is a UTC Unix timestamp of when the JWT was issued.
**'''exp''': Expiration. A UTC Unix timestamp of when the JWT should expire.
**'''exp''': (mandatory) Expiration. A UTC Unix timestamp of when the JWT should expire.
**'''nbf''': (optional) Not-before time. A UTC Unix timestamp of the earliest time the JWT can be processed.
**'''request''' (mandatory): Request object.
**'''request''' (mandatory): Request object.
*** '''price''' (mandatory): List of prices per currency.
*** '''pricePoint''' (mandatory): An identifier that corresponds to a price according to the Payment Provider. For example, pricePoint 1 might translate into €0.89 when the buyer is in Europe or $0.99 when in the US, etc. The exact price point values are managed by the Payment Provider and they may change based on currency exchange rates.
**** '''currency''': Currency code expressed following the recommendations from [http://www.xe.com/iso4217.php/ ISO 4217].
**** '''amount''': Value withouth taxes of the digital good being sold.
*** '''defaultPrice''': This is the currency code for the default price to be considered as payment price when there is not matching between the customer currency and the currency prices located in the price collection. This must match with one of the currency codes in the price list.  
*** '''name''' (mandatory): A short description of the product.
*** '''name''' (mandatory): A short description of the product.
*** '''description''' (mandatory): A long description of the product.
*** '''description''' (mandatory): A long description of the product.
*** '''id''' (mandatory): A unique identifier for the product you are selling. This only needs to be unique within your own catalog, not unique among all products from all apps.
*** '''id''' (mandatory): A unique identifier for the product you are selling. This only needs to be unique within your own catalog, not unique among all products from all apps.
*** '''productData''': A freeform string, no longer than 255 characters. This can be anything the app might need to identify the product with when a postback is sent back to the app.
*** '''productData''' (optional): A freeform string, no longer than 255 characters. This can be anything the app might need to identify the product with when a postback is sent back to the app.
*** '''postbackURL''': URL where the payment processor sends an HTTP POST message to whenever a purchase completes. The application server needs to acknowledge these POST messages, or else the transactions will be canceled. If the payment processor stores a different postback URL, this one should override the stored one. This way, application developer could choose different endpoints for different kind of digital goods.
*** '''postbackURL''' (mandatory): URL where the payment processor sends an HTTP POST message to whenever a purchase completes. The application server needs to acknowledge these POST messages, or else the transactions will be canceled.
*** '''chargebackURL''': URL where the payment processor sends an HTTP POST message to whenever a refund associated with this transaction is done. If the payment processor stores a different chargeback URL for this application, the one within the JWT should override the stored one.
*** '''chargebackURL''' (mandatory): URL where the payment processor sends an HTTP POST message to whenever a refund associated with this transaction is done.


* For a user to make a purchase, the app must execute the Javascript method <code>navigator.mozPay()</code> with this signed payment request. For example, the app might have a 'buy' button that triggers this method when clicked. Then <code>navigator.mozPay</code> method should take the signed payment JWT or an array of them. As a result of this call a [https://developer.mozilla.org/en/DOM/DOMRequest DOMRequest] object would be returned. The developer may use this DOMRequest object to monitor the progress of the operation.
* For a user to make a purchase, the Application must execute the Javascript method <code>navigator.mozPay()</code> with one or more signed payment requests (the JWTs). For example, the app might have a 'buy' button that triggers this method when clicked. Then <code>navigator.mozPay</code> method should take the signed payment JWT or an array of them. It will return a [https://developer.mozilla.org/en/DOM/DOMRequest DOMRequest] object that the developer can use to monitor the progress of the operation.
<code>
<code>
let request = navigator.mozPay([signedJWT1, signedJWTn]);
  var request = navigator.mozPay([signedJWT1, signedJWTn]);
request.onsuccess = function () {
  request.onsuccess = function () {
  [...]
    // The payment buy flow completed without errors.
}
    // This does NOT mean the payment was successful.
request.onerror = function () {
    waitForServerPostback();
  [...]
  }
}
  request.onerror = function (errorMsg) {
</code>
    console.log('navigator.mozPay() error: ' + this.error.name + ': ' + errorMsg);
* The <code>navigator.mozPay</code> method must open a payment request confirmation screen based on the received JWTs, so the user can confirm and choose the payment method that is more appropriate for him. B2G would only show the payment providers for the JWT '''typ''' values that are pre-registered in the UA. JWTs containing a '''typ''' value not registered in the UA would be considered as invalid.
  }
* Once the user selects a Payment Provider the UA must open a trusted dialog ("chrome" dialog) containing the Payment Provider's buy flow that is registered in the UA for the '''typ''' value of the passed JWT (Telefónica ID in case of BlueVia).
</code>
* User authentication can be done via network authentication (MSISDN) or BrowserID assertion. Authorization could also be done with user password or PIN code sent via SMS. The authentication and authorization mechanisms are reponsibility of the Payment Provider and it is beyond the scope of this document.
* The <code>navigator.mozPay</code> method will open a payment request confirmation screen based on the received JWTs, so the user can confirm and choose the payment method that is more appropriate for him. B2G would only show the payment providers for the JWT '''typ''' values that are pre-registered in the User Agent. JWTs containing a '''typ''' value not registered in the UA would be considered as invalid.
* The purchase is presented to the user for confirmation, with name, description and identifying information from Telefónica ID about the seller.
* Once the user selects a Payment Provider the UA will open a special window (a "chrome" dialog) containing the Payment Provider's buy flow that is registered in the User Agent for the '''typ''' value of the passed JWT (e.g. Firefox Marketplace in the example above).
* Why an array of JWTs? The Developer will have a specific contract with each Payment Provider and will have a unique Application Secret for each provider. When a payment is initiated, the Application must send all JWTs. The user will select only one Payment Provider to complete the purchase. If there is only one JWT to choose from, the user will automatically use that Payment Provider, and this is the case for the first version of B2G.
* The details of the exact buy flow are implemented by the [https://wiki.mozilla.org/WebAPI/WebPaymentProvider Payment Provider]. In Mozilla's pay flow, the user logs in via Persona, enters a PIN, and is presented with details about the item to be purchased.
* The user confirms the purchase or cancels it.
* The user confirms the purchase or cancels it.
* If the user cancels or something goes wrong with the payment process, the flow returns to the <code>DOMRequest.onerror</code> callback.
* If the user cancels or something goes wrong with the payment process, the flow returns to the <code>DOMRequest.onerror</code> callback.
* If the user confirms, the Payment Provider should send a POST confirmation message (a JWT) to the postback URL of the seller provided within the JWT request. The postback URL is optional, but strongly recommended. The Payment Processor (also via Mozilla Marketplace) could optionally store a default postback URL as part of his application sign-up process. If a different postback URL is provided within the JWT request, this one would override the default one stored as default in the Payment Provider. This confirmation message contains all the payment request fields (for the '''price''' case, only the selected value is returned) plus a transaction identifier, and is signed with the seller's application secret. In the following example, the ''transactionID'' field is specific to the BlueVia payment provider.
<code>
  {
    "iss" : "tu.com",
    "aud" : sellerIdentifier,
    "typ" : "tu.com/payments/v1/pay",
    "exp" : 1337370900,
    "iat" : 1337360900,
    "request" : {
      "name" : "Piece of Cake",
      "description" : "Virtual chocolate cake to fill your virtual tummy",
      "id": "unique-id-for-product",
      "price" : [{
        "currency": "BRL",
        "amount": "11.07"
      }],
      "productData" : "my_product_id=1234&my_session_id=XYZ",
      "postbackURL" : "http://developersserver.com/postback",
      "chargebackURL" : "http://developerserver.com/chargeback"
    },
    "response": {
      "transactionID": 123456123456123456
    }
  }
</code>


* To verify that the purchase is valid, the application server first needs to decode and verify the JWT in the POST. If the purchase is valid, then the application server should record it and respond with a 200 OK that contains the order ID. The transaction '''should''' be canceled by the Payment Provider if the server take longer than x seconds to send a 200 OK response.
=== Notifications ===


<span style="color:#800000">The JWT format and callbacks above are compatible with Google's in-app payment system, as well as Mozilla's paypal-based in-app payment flow.</span>
The Application must only rely on server side notifications to determine the outcome of a purchase. The Payment Provider will POST a confirmation message (a JWT) to the '''postbackURL''' (on success) or the '''chargebackURL''' (on error). The Application provides these URLs in the original JWT request. On a production server, these URLs must be HTTPS.  


[[File:WebPaymentAppPaymentFlow.png|600px|thumb|center|Buy flow]]
The POST request will have a content-type of <code>application/x-www-form-urlencoded</code> and the JWT will be in the '''notice''' form parameter. This JWT contains a copy of the original payment request plus a new response object that has a '''transactionID''' which identifies the Payment Provider's transaction.


=== Marketplace payment ===
When a JWT is received, the Application first needs to verify the signature using its Application Secret. If the signature is not valid, it probably was not sent from the Payment Provider and should be ignored. If the signtature is valid, then the application server should decode the JWT, record it, and '''respond with a 200 OK that contains the transactionID in plain text'''. If the Application server responds with an error status or does not respond with the right transactionID, the Payment Provider will consider this a failure. It will retry and/or notify the Developer about the failure. The Application must respond to the request in plain text containing just the '''transactionID''' value.
''(This is just a proposal and not the current Marketplace scenario)''


The Mozilla Marketplace use <code>navigator.mozPay</code> to execute purchase transactions for apps. The only difference to in-app payments is that the Marketplace use the app as the issuer (recipient of funds), instead of itself.
==== Postback ====


Once the user starts an application purchase process, the marketplace generates a payment request that contains all the information about the application being purchased: price, currency, name, description and so on, with the addition of the '''application key''' that identifies the '''developer''' as issuer of the transaction (recipient of funds) and the marketplace '''postback''' and '''chargeback''' URLs, so the '''Marketplace''' could receive notifications about the transaction result or refunds associated with the transaction. The marketplace signs the payment request with the application secret.
Here is an example of a JWT POSTed via the '''notice''' parameter to '''postbackURL''' that indicates a transaction was fully processed and was successful:


<code>
<code>
   {
   {
     "iss": sellerIdentifier, // developer identifier in this case
     "iss": "marketplace.firefox.com",
     "aud": "tu.com",
     "aud": APPLICATION_KEY,
     "typ": "tu.com/payments/v1/pay",
     "typ": "mozilla/payments/pay/postback/v1",
     "exp": 1337370900,
     "exp": 1337370900,
     "iat": 1337360900,
     "iat": 1337360900,
     "request": {
     "request": {
      "name": "Demo app",
       "id": "915c07fc-87df-46e5-9513-45cb6e504e39",
      "description": "Demo app",
       "pricePoint": 1,
       "id": "unique-app-id",
       "name": "Magical Unicorn",
       "price": [
       "description": "Adventure Game item",
       {
       "productData": "user_id=1234&my_session_id=XYZ",
        "currency": "USD",
       "postbackURL": "https://yourapp.com/payments/postback",
        "amount": "0.99"
       "chargebackURL": "https://yourapp.com/payments/chargeback"
      },  
    },
       {
    "response": {
        "currency": "BRL",
       "transactionID": "webpay:84294ec6-7352-4dc7-90fd-3d3dd36377e9"
        "amount": "1.99"
      }],
       "defaultPrice": "USD",
       "postbackURL": "https://marketplace.mozilla.org/postback",
       "chargebackURL": "https://marketplace.mozilla.org/chargeback",
       "productData": "my_product_id=123"
     }
     }
   }
   }
</code>
</code>


The buy flow is the same as the in-app payments case for the payment processor's side. As mentioned before, the difference to in-app payments requests is that a marketplace request must contain a postback url in order to notify the market about the successful transaction. Once the marketplace receives the details of the transaction it should unlock the application installation, and it could optionally notify the developer about it via the registered postback url for the application.
Here is an example response that includes just the transactionID:


== Refunds ==
  <code>
Refunds depend on the technical capabilities of the payment provider. If technically feasible, the UA '''should''' direct users to a Payment Provider-hosted refund page (trusted dialog launched from the settings area) that lists the most recent purchases and allows to cancel them. Authentication occurs via MSISDN or BrowserId as for payments.
  HTTP/1.1 200 OK
  Content-Type: text/plain
 
  webpay:84294ec6-7352-4dc7-90fd-3d3dd36377e9
  </code>


=== Refunds for in-app purchases ===
==== Chargeback ====
The <code>navigator.mozPay</code> API allows refunds the same way as it allow purchases, so the developer has the choice of exposing a way to request refunds from his application. The difference between a purchase and a refund is within the JWT request parameters. The content of this refund requests depend on the needs of the Payment Provider. For the BlueVia case, a ''transaction ID'', a ''reason'' and an optional ''chargeback URL'' parameters are required.
<code>
  {
    "iss" : sellerIdentifier,
    "aud" : "tu.com",
    "typ" : "tu.com/payments/v1/refund",
    "exp" : 1337370900,
    "iat" : 1337360900,
    "request" : {
      "refund": "123456123456123456", // transaction ID
      "reason": "refund",
      "chargebackURL": "https://developerserver.com/chargeback"
    }
  }
</code>


The developer would receive a chargeback notification containing the details of the refund operation.
Here is an example of a JWT POSTed via the '''notice''' parameter to '''chargebackURL''' that indicates a transaction was fully processed but was unsuccessful:


<code>
<code>
   {
   {
    "iss" : "tu.com",
    "iss": "marketplace.firefox.com",
    "aud" : sellerIdentifier,
    "aud": APPLICATION_KEY,
    "typ" : "tu.com/payments/v1/refund",
    "typ": "mozilla/payments/pay/chargeback/v1",
    "exp" : 1337370900,
    "exp": 1337370900,
    "iat" : 1337360900,
    "iat": 1337360900,
    "request" : {
    "request": {
      "name" : "Piece of Cake",
      "id": "915c07fc-87df-46e5-9513-45cb6e504e39",
      "description" : "Virtual chocolate cake to fill your virtual tummy",
      "pricePoint": 1,
      "id": "unique-product-id",
      "name": "Magical Unicorn",
      "price" : [
      "description": "Adventure Game item",
      {
      "productData": "user_id=1234&my_session_id=XYZ",
        "currency": "USD",
      "postbackURL": "https://yourapp.com/payments/postback",
        "amount": "5.50"
      "chargebackURL": "https://yourapp.com/payments/chargeback"
      },  
    },
      {
    "response": {
        "currency": "BRL",
      "transactionID": "webpay:84294ec6-7352-4dc7-90fd-3d3dd36377e9",
        "amount": "11.07"
      "reason": "refund"
      }],
    }
      "defaultPrice": "USD",
      "productData": "my_product_id=123",
      "postbackURL": "http://developerserver.com/postback",
      "chargebackURL": "http://developerserver.com/chargeback"
    },
    "response" : {
      "transactionID": "123456123456123456", // transaction ID
      "reason": "refund"
    }
   }
   }
</code>
</code>


More details about the chargeback notifications are explained in the [https://wiki.mozilla.org/WebAPI/WebPayment#Notifications Notifications] section.
A chargeback JWT might be received instead of or in addition to a postback. The response will contain a '''reason''' attribute, as follows:
; refund: The payment was refunded either upon request of the customer or by an administrator.
; reversal: A buyer has asked the credit card issuer to reverse a transaction after it has been completed. The buyer might do this through the credit card company as part of a dispute.


Apart from the <code>navigator.mozPay()</code> option for requesting refunds, the Payment Processor (BlueVia) '''SHOULD''' keep a list of transactions accessible via user profile, allowing the user to cancel the latest ones. A chargeback notification should be sent to the developer once a refund is requested by the user and executed by the Payment Processor systems. The way the developer handles the chargeback notification would depend on the nature of the sold digital good and it is developer's responsibility to cancel or remove it properly from the user's device. Go to [https://developer.mozilla.org/en-US/docs/Apps/Validating_a_receipt Validating a receipt] for information about how the Mozilla Marketplace deals with apps receipts.
Here is an example response that includes just the transactionID:


=== Marketplace refunds for application purchases ===
  <code>
The Marketplace must keep a purchase history allowing the user to cancel the latest transactions and request a refund for it.
  HTTP/1.1 200 OK
The way the Marketplace would handle the refund requests from the user would depend on the Payment Provider used for each specific transaction. Currently, the Marketplace is only supporting one Payment Provider: Telefónica BlueVia. The PayPal case got out of the scope of v1 release.
  Content-Type: text/plain
 
  webpay:84294ec6-7352-4dc7-90fd-3d3dd36377e9
  </code>


For the BlueVia use case, in order to request refunds for application purchases the Marketplace would use the same <code>navigator.mozPay</code> API. Just like for the Marketplace payment flow via BlueVia Payment Provider, the only difference to in-app refunds is that the Marketplace use the app as the issuer, instead of itself.
== Refunds ==


Once the user requests a refund for an application purchased, the Marketplace generates a refund request that contains the transactionID returned as result of the payment flow for that application. The marketplace can also add a chargeback URL, so it could receive notifications about the refund process result in this URL. The new chargeback URL is optional and you may found more info about this in the [https://wiki.mozilla.org/WebAPI/WebPayment#Notifications Notifications] section. Finally, the marketplace should sign the refund request with the application secret.
Refunds are not yet supported by the <code>navigator.mozPay</code> API. '''At a future date, an Application may be able to request a refund like this''':


<code>
<code>
   {
   {
    "iss" : developerIdentifier,
    "iss": APPLICATION_KEY,
    "aud" : "tu.com",
    "aud": "marketplace.firefox.com",
    "typ" : "tu.com/payments/refund/v1",
    "typ": "mozilla/payments/refund/v1",
    "exp" : 1337370900,
    "exp": 1337370900,
    "iat" : 1337360900,
    "iat": 1337360900,
    "request" : {
    "request": {
      "refund" : "123456177777777", // transaction ID associated to app purchase flow
      "transactionID": "webpay:84294ec6-7352-4dc7-90fd-3d3dd36377e9",
      "reason" : "Application refund",
      "reason": "User requested refund",
      "chargebackURL" : "https://marketplace.mozilla.org/chargeback"
      "chargebackURL": "https://yourapp.com/payments/chargeback"
    }
    }
   }
   }
</code>
</code>


Once the refund request is processed and accepted by the Payment Provider, it would send the chargeback notification to the Marketplace. The Marketplace must notify the developer about this refund using the notification information that the developer may have provided during his registration process, if available.
This would initiate a refund flow and POST a confirmation JWT when completed.
 
Applications should be uninstalled from the user's device after canceling the associated transaction by invalidating the application receipt.
 
== Notifications ==
 
=== Developer notifications ===
A developer that wants to order a payment and receive notifications about the payment and the potential refund related to that transaction has the following options:
# As part of his registration process, he may want to provide a '''default postback''' and a '''default chargeback''' URLs, so he would be notified about app purchases and refunds on this endpoints. This URLs would be provided and editable via the Mozilla Marketplace, which also would be responsible for notifying the developer about app purchases and refunds. This default URLs info would be shared with the Payment Provider, which would use it to notify the developer about in-app purchases or refunds related to in-app purchases.
# If the developer didn't provide his default postback and chargeback URLs or '''wants to temporary override this default URLs''', he can also add new URLs to his payment request. If a postback URL is provided within the payment request, the Payment Provider would use this callback to notify the developer about the successful or failed transaction. If a chargeback URL is provided within the payment request, the Payment Provider would associate this URL with the transaction ID, so  if a refund is requested with that transaction ID, the Payment Provider would use that chargeback URL to notify the developer about the refund (and not the default one stored for the developer, if available) unless 3.
# If a developer provides a new chargeback URL within his refund request, this last URL would be the one used by the Payment Provider to notify about the successful or failed refund.
 
So the basic idea is: '''the latest URL received by the Payment Provider is the last used for notifications.'''
 
=== Marketplace notifications ===
As you already know, the Marketplace would use the <code>navigator.mozPay()</code> to request payments and refunds as any other developer would. The main difference is that the Marketplace would request payments and refunds on behalf of the developer. So it would need to add the appID on its payment and refund requests to identify the developer.
 
Besides that, as the Marketplace wants to be notified about app purchases and refunds related to that purchases, it would need to use option 2 mentioned before. That means, it would need to include it postback and chargeback URL, so the Payment Provider would notify the Marketplace instead of the developer. Once the Marketplace is notified by the Payment Provider, it would need to notify the developer via the default URL that he might have provided.
 
== Online payments from other networks ==
Users can pay over wifi connections if the Payment Provider integrates with BrowserID or a SMS verification flow is provided. In this case, payments actually can occur via other devices and the same BrowserID (e.g. by a user logged into a desktop browser with the same identity that is tied to a carrier account).
 
== Developer control ==
The Payment Provider can expose an API to share aggregated transaction info with the Marketplace to allow developers to keep track of app sales from within the Marketplace. The Marketplace can aggregate this information from all the Payment Providers the developer signed up for for the particular app.


= Payment Provider facing API =
= Payment Provider facing API =
Line 320: Line 226:


= Testing =
= Testing =
== Test app ==
https://github.com/ferjm/Payment-tests
== Testing against the Mock Payment Provider ==
The <code>navigator.mozPay</code> API is currently in mozilla-central and there is a dummy mock payment provider available for testing.


<code>navigator.mozPay</code> requires a list of allowed Payment Providers given in the form of user preferences. So first of all, you need to add the Mock Payment Provider information to your Gaia profile as a user preference:
[https://developer.mozilla.org/en-US/docs/Mozilla/Firefox_OS Firefox OS] and the [https://developer.mozilla.org/en-US/docs/Mozilla/Firefox_OS/Using_Firefox_OS_Simulator Firefox OS Simulator] ship only with settings to make real payments. If you want to test payments, you can use the simulation feature [https://developer.mozilla.org/en-US/docs/Apps/Publishing/In-app_payments described here]. To test with a custom Payment Provider you need to adjust your settings.
- Create a file named <code>custom-prefs.js</code> in the Gaia root directory.
- Add the following user preferences:
<code>
user_pref("dom.payment.provider.0.name", "mockpayprovider");
user_pref("dom.payment.provider.0.description", "Mock Payment Provider");
user_pref("dom.payment.provider.0.type", "mock/payments/inapp/v1");
user_pref("dom.payment.provider.0.uri", "https://mockpayprovider.phpfogapp.com/?req=");
user_pref("dom.payment.provider.0.requestMethod", "GET");
</code>
Note that you may need to change the payment provider index (0 in the above preferences) according to already existing payment provider preferences.
- Run <code>make install-gaia</code>


After adding the Mock Payment Provider information as a user preference you should be able to start testing.
Consult the [https://developer.mozilla.org/en-US/docs/Mozilla/Firefox_OS/Using_the_B2G_desktop_client#Generating_a_profile B2G guide] for how to set custom settings on a device.


An example of a valid payment request against the Mock Payment Provider would be:
Here is a helpful setting to disable HTTPS checks for testing:


<code>
<code>
{
   pref("dom.payment.skipHTTPSCheck", true);
   "iss": "123456789",
  "aud": "Mock Payment Provider",
  "typ": "mock/payments/inapp/v1",
  "exp": 1345259882,
  "iat": 1345256282,
  "request": {
    "name": "Piece of Cake",
    "description": "Virtual chocolate cake to fill your virtual tummy",
    "id": "unique-product-id",
    "price": [
        { "country":"US", "amount":"5.50", "currency":"USD" },
        { "country":"BR", "amount":"8.50", "currency":"BRL" }
    ]
  }
}
</code>
</code>


Which results in the following JWT:
Here is a rough example of how you could add a custom Payment Provider:
 
<code>eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiIxMjM0NTY3ODkiLCJhdWQiOiJNb2NrIFBheW1lbnQgUHJvdmlkZXIiLCJ0eXAiOiJtb2NrXC9wYXltZW50c1wvaW5hcHBcL3YxIiwiZXhwIjoxMzQ1MjU5ODgyLCJpYXQiOjEzNDUyNTYyODIsInJlcXVlc3QiOnsibmFtZSI6IlBpZWNlIG9mIENha2UiLCJkZXNjcmlwdGlvbiI6IlZpcnR1YWwgY2hvY29sYXRlIGNha2UgdG8gZmlsbCB5b3VyIHZpcnR1YWwgdHVtbXkiLCJwcmljZSI6W3siY291bnRyeSI6IlVTIiwiYW1vdW50IjoiNS41MCIsImN1cnJlbmN5IjoiVVNEIn0seyJjb3VudHJ5IjoiQlIiLCJhbW91bnQiOiI4LjUwIiwiY3VycmVuY3kiOiJCUkwifV19fQ.EaXnlL7LUlmYXUTty5ZkUQ7VZeCBa_edi2YXKPnjSl4</code>
 
== Testing against the BlueVia Payment Provider ==
* Since the BlueVia Payment Provider is behind a Telefónica VPN, the first requirement is to get access to it. Unfortunately not everyone is allowed to access to the BlueVia servers and a specific request to the BlueVia team has to be done in order to request access.
 
* Since B2G fails ([https://bugzilla.mozilla.org/show_bug.cgi?id=769178 Bug 769178]) when accessing some HTTPS urls that have a certificate signed by an unknown CA (like the BlueVia one), apart from access to the BlueVia dev server, you would also need the BlueVia dev server certificates. BlueVia should probably be changing its certificate for a different one signed by a public CA, but until then, you would need to install the current certificates.
** If you are testing on a <b>B2G device</b>, you can simply run this [http://openwebdevice.com/blueviacerts/modifyCertDb.zip script] (just run <code>./modifyCertDb.sh</code> in Mac or Linux, or the .bat file in Windows) which would install the certificates and add the BlueVia payment provider information to the Gaia profile.
** Unfortunately, there is not an automatic script for the <b>B2G-desktop</b> environment, and the certificate instalation requires the following steps:
*** From the shell that you are going to run B2G desktop do <code>export NSS_DEFAULT_DB_TYPE=sql</code>
*** Remove the <code>key3.db</code> and <code>cert8.db</code> files from your Gaia profile root.
*** Run B2G via <code>./b2g -profile <path-to-gaia-profile></code>
*** Open an HTTPS web page from the Gaia browser app.
*** Close B2G-desktop.
*** Download this [http://openwebdevice.com/blueviacerts/modifyCertDb.zip zip] file, extract its content and copy the <code>key4.db</code>, <code>cert9.db</code> and <code>pkcs11.txt</code> files to the Gaia profile root.
*** Launch B2G-desktop again. You should be able to navigate to https://webvpn.tid.es from the Gaia system app, that means that the certificates has been properly stored.
*** Add the following preferences to your Gaia profile (as explained in [Testing against a Mock Payment Provider]):
<code>
user_pref("dom.payment.provider.0.name", "blueviasimple");
user_pref("dom.payment.provider.0.description", "Telefónica BlueVia simple flow");
user_pref("dom.payment.provider.0.type", "tu.com/payments/v1/simplepay");
user_pref("dom.payment.provider.0.uri", "https://id.tda-qa-01.hi.inet/en/payments/v1/simplepay?req=");
user_pref("dom.payment.provider.0.requestMethod", "GET");
user_pref("dom.payment.provider.1.name", "bluevia");
user_pref("dom.payment.provider.1.description", "Telefónica BlueVia flow");
user_pref("dom.payment.provider.1.type", "tu.com/payments/v1/pay");
user_pref("dom.payment.provider.1.uri", "https://id.tda-qa-01.hi.inet/en/payments/v1/pay?req=");
user_pref("dom.payment.provider.1.requestMethod", "GET");
</code>
 
If everything went well, you should be able to start testing against BlueVia.
 
An example of a valid payment request against the BlueVia Payment Provider would be:


<code>
<code>
{
   pref("dom.payment.provider.1.name", "mockpayprovider");
   "iss":"567744a02bf5af6a8c4c1c80d22fb2",
   pref("dom.payment.provider.1.description", "Mock Payment Provider");
  "typ":"tu.com/payments/v1/pay",
  pref("dom.payment.provider.1.type", "mock/payments/inapp/v1");
  "exp":3337360897,
  pref("dom.payment.provider.1.uri", "https://mockpayprovider.phpfogapp.com/?req=");
   "iat":1337360897,
  pref("dom.payment.provider.1.requestMethod", "GET");
  "request": {
    "name": "Piece of Cake",
    "description": "Virtual chocolate cake to fill your virtual tummy",
    "id": "unique-product-id",
    "price": [
        { "country":"BR", "amount":"1", "currency":"BRL" },
        { "country":"ES", "amount":"0.01", "currency":"EUR" }
    ],
    "defaultPrice": "US",
    "productData": "my_product_id=1234&my_session_id=XYZ",
    "postbackURL": "http://developersserver.com/postback",
    "chargebackURL": "http://developerserver.com/chargeback"
  }
}
</code>
</code>


Which results in the following JWT:
== Test apps ==
<code>eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiI1Njc3NDRhMDJiZjVhZjZhOGM0YzFjODBkMjJmYjIiLCJ0eXAiOiJ0dS5jb21cL3BheW1lbnRzXC92MVwvcGF5IiwiZXhwIjozMzM3MzYwODk3LCJpYXQiOjEzMzczNjA4OTcsInJlcXVlc3QiOnsibmFtZSI6IlBpZWNlIG9mIENha2UiLCJkZXNjcmlwdGlvbiI6IlZpcnR1YWwgY2hvY29sYXRlIGNha2UgdG8gZmlsbCB5b3VyIHZpcnR1YWwgdHVtbXkiLCJwcmljZSI6W3siY291bnRyeSI6IkJSIiwiYW1vdW50IjoiMSIsImN1cnJlbmN5IjoiQlJMIn0seyJjb3VudHJ5IjoiRVMiLCJhbW91bnQiOiIwLjAxIiwiY3VycmVuY3kiOiJFVVIifV0sImRlZmF1bHRQcmljZSI6IlVTIiwicHJvZHVjdERhdGEiOiJteV9wcm9kdWN0X2lkPTEyMzQmbXlfc2Vzc2lvbl9pZD1YWVoiLCJwb3N0YmFja1VSTCI6Imh0dHA6IFwvXC9kZXZlbG9wZXJzc2VydmVyLmNvbVwvcG9zdGJhY2siLCJjaGFyZ2ViYWNrVVJMIjoiaHR0cDogXC9cL2RldmVsb3BlcnNlcnZlci5jb21cL2NoYXJnZWJhY2sifX0.b6n2j--3vyi1szMqsyXkldeUK5G0HWv4w_G7u1dXmdY</code>
* https://github.com/ferjm/Payment-tests
* https://github.com/kumar303/inapp-pay-test


= Current implementation =
= Current implementation =
Line 467: Line 297:
== Communication ==
== Communication ==
The communication between Gecko and Gaia is done via <code>mozChromeEvent</code> and <code>mozContentEvent</code>, which is a communication mechanism between chrome and the Gaia system app. <code>mozChromeEvent</code> are sent from chrome to content and <code>mozContentEvent</code> are sent from content to crhome. Where "sent" is used loosely here, since "broascast" is probably a better term. This communication is required to control the creation and closure of the trusted UI and to inject the required functions to notify the success or failure of the payment flow within the payment provider content.
The communication between Gecko and Gaia is done via <code>mozChromeEvent</code> and <code>mozContentEvent</code>, which is a communication mechanism between chrome and the Gaia system app. <code>mozChromeEvent</code> are sent from chrome to content and <code>mozContentEvent</code> are sent from content to crhome. Where "sent" is used loosely here, since "broascast" is probably a better term. This communication is required to control the creation and closure of the trusted UI and to inject the required functions to notify the success or failure of the payment flow within the payment provider content.
= Helper Libraries =
These are specific <code>navigator.mozPay()</code> libraries:
* [http://mozpay.readthedocs.org/en/latest/ mozpay-py], a Python library
* [https://github.com/mozilla/mozpay-js mozpay-js], a NodeJS library
These are generic JWT libraries that you can use:
* [https://pypi.python.org/pypi/PyJWT-mozilla PyJWT-mozilla]
* [https://github.com/progrium/ruby-jwt ruby-jwt]
* [https://github.com/hokaccha/node-jwt-simple node-jwt-simple]
* [https://github.com/luciferous/jwt PHP luciferous/jwt]
* [http://code.google.com/p/jsontoken/ Java jsontoken]
* [http://json.codeplex.com/ JSON.NET]


= See also =
= See also =
Line 474: Line 318:
* [https://wiki.mozilla.org/Security/Reviews/navigator.pay Security review]
* [https://wiki.mozilla.org/Security/Reviews/navigator.pay Security review]
* [https://docs.google.com/document/d/1EBkzFye6DqCQkMEkXkjLjMvaYYCqvCGJrYhyG38R4DU/edit#heading=h.q13jnkaxpfle Threat model]
* [https://docs.google.com/document/d/1EBkzFye6DqCQkMEkXkjLjMvaYYCqvCGJrYhyG38R4DU/edit#heading=h.q13jnkaxpfle Threat model]
* [https://developer.mozilla.org/en/Apps/In-app_payments Marketplace In-app payments proposal]
* [https://developer.mozilla.org/en-US/docs/Apps/Publishing/In-app_payments Firefox Marketplace In-app payments]
* [https://wiki.mozilla.org/B2G/QA/WebAPI_Test_Plan/WebPayment Test Plan]
* [https://wiki.mozilla.org/B2G/QA/WebAPI_Test_Plan/WebPayment Test Plan]
* [https://github.com/mozilla/apps-payment-server/ Original Labs Prototype]
* [https://github.com/mozilla/apps-payment-server/ Original Mozilla Labs Prototype]


= Similar APIs =
= Similar APIs =
* [https://developers.google.com/commerce/wallet/digital/ Google Wallet's in-app payment API]
* [https://developers.google.com/commerce/wallet/digital/ Google Wallet's in-app payment API]
* [https://developer.apple.com/appstore/in-app-purchase/index.html iOS in-app payment API]
* [https://developer.apple.com/appstore/in-app-purchase/index.html iOS in-app payment API]

Revision as of 01:13, 8 March 2013

WebPayment API Specification (DRAFT)

Goal

The goal of this API is to enable web content to collect payment (or issue a refund) for a virtual good via the navigator.mozPay() function.

Status

See bug 767818 for the navigator.mozPay() implementation.

Currently only implemented for B2G.

Proposed API

Expose the pay function to the navigator object

interface nsIDOMNavigatorPayment
{
  DOMRequest mozPay(in jsval jwts);
}

Proposers

Andreas Gal and Fernando Jiménez. Based on Mike Hanson's and Kumar McMillan's work for Firefox Marketplace in-app payments.

WebPayments Architecture (DRAFT)

Introduction

An open web app will interact with a Payment Provider via navigator.mozPay() and receive notifications POSTed to its server about the result of each payment. Users will see a payment flow hosted by the Payment Provider in a special window on the device.

Payment flow overview

  • The app initiates a payment by signing a JSON Web Token (JWT) request and calling navigator.mozPay().
  • This starts the buyflow in a content iframe inside a trusted dialog ("chrome dialog").
  • A purchasing flow is served from the Payment Provider's server as an HTML5 document inside the trusted dialog.
  • The buyer is authenticated by the Payment Provider (via the network (radio), Mozilla Persona assertion or whatever authentication mechanisms the Payment Provider chooses).
  • The buyer completes or cancels the purchase.
  • The app receives a Javascript callback when the buyer completes or cancels the purchase.
  • The app server receives a signed POST request with a Payment Provider transaction identifier indicating that the purchase was completed successfully (or it failed).

Detailed payment flow

This detailed payment flow is based on Mozilla's initial implementation for Firefox OS but it could be extended to any other Payment Provider.

Definitions

Application
Open Web App which offers digital goods to be sold. OWAs charge users via navigator.mozPay() and require a client and server.
Application Key
A public identifier that can be transmitted in a JSON object so that a Payment Provider can identify the Application.
Application Secret
A private string shared between Developer and Payment Provider. This will be used to sign JSON Web Tokens (JWTs) and must be securely protected on a web server.
Developer
Application developer, seller of digital goods.
Firefox Marketplace
Developer portal and application repository. Developers can submit apps to the Firefox Marketplace so users can purchase and download the apps. The Firefox Marketplace uses the navigator.mozPay function to charge users for application purchases.
Payment Provider
A client/server web application that serves content in a special iframe controlled by navigator.mozPay(). This conforms to the Payment Provider spec. The provider accepts payment from a user and disperses income to the Developer.
User
End user who wants to purchase a digital good.
User Agent
B2G (Firefox OS)

User sign-in

This is an implementation detail of the Payment Provider. The navigator.mozPay() API does not prescribe any user authorization scheme.

In Mozilla's Payment Provider implementation, users will sign-in via Persona.

Developer registration

This is an implementation detail of the Payment Provider. The navigator.mozPay() API facilities two parties -- a Developer and a Payment Provider -- in making a transaction but it does not facilitate any part of the registration process.

A developer who creates a JSON Web Token must do so with an Application Secret obtained from the Payment Provider. In Mozilla's implementation, a developer signs up through the Firefox Marketplace Developer Hub, enters bank account details for payouts, obtains an Application Key and Application Secret, and can begin signing JWTs for purchase.

Initiating a Payment

  • The user opens an Application
  • The user finds something interesting to purchase. Let's say it's a game and the user wants to purchase a Magical Unicorn to excel in the game.
  • The user clicks a Buy Unicorn button.
  • The Application server responds by first signing a JSON Web Token (JWT) using its Application Secret. The JWT is a base64-encoded signed JSON object. The JSON contains all the details about the product such as name, description, price, etc.
  • The Application bubbles up the JWT to the client and calls navigator.mozPay([theJWT]). This begins the hosted buy flow within a special window on the User Agent.

Example of server-side JWT generation in Python using PyJWT-mozilla:

 paymentJWT = jwt.encode({
   "iss": APPLICATION_KEY,
   "aud": "marketplace.firefox.com",
   "typ": "mozilla/payments/pay/v1",
   "iat": 1337357297,
   "exp": 1337360897,
   "request": {
     "id": "915c07fc-87df-46e5-9513-45cb6e504e39",
     "pricePoint": 1,
     "name": "Magical Unicorn",
     "description": "Adventure Game item",
     "productData": "user_id=1234&my_session_id=XYZ",
     "postbackURL": "https://yourapp.com/payments/postback",
     "chargebackURL": "https://yourapp.com/payments/chargeback"
   }
 }, APPLICATION_SECRET)

Here is a detailed explanation of the payment JWT:

    • iss (mandatory): The issuer of the JWT. This is the Application Key assigned during the app registration process for this specific payment provider (e.g. Firefox Marketplace).
    • typ (mandatory): The JWT type. This identifies the payment provider, e.g. Firefox Marketplace, and the JWT version that must be supported by the provider. On the User Agent, typ is used to look up white-listed Payment Providers.
    • iat: (mandatory) Issued at time. This is a UTC Unix timestamp of when the JWT was issued.
    • exp: (mandatory) Expiration. A UTC Unix timestamp of when the JWT should expire.
    • nbf: (optional) Not-before time. A UTC Unix timestamp of the earliest time the JWT can be processed.
    • request (mandatory): Request object.
      • pricePoint (mandatory): An identifier that corresponds to a price according to the Payment Provider. For example, pricePoint 1 might translate into €0.89 when the buyer is in Europe or $0.99 when in the US, etc. The exact price point values are managed by the Payment Provider and they may change based on currency exchange rates.
      • name (mandatory): A short description of the product.
      • description (mandatory): A long description of the product.
      • id (mandatory): A unique identifier for the product you are selling. This only needs to be unique within your own catalog, not unique among all products from all apps.
      • productData (optional): A freeform string, no longer than 255 characters. This can be anything the app might need to identify the product with when a postback is sent back to the app.
      • postbackURL (mandatory): URL where the payment processor sends an HTTP POST message to whenever a purchase completes. The application server needs to acknowledge these POST messages, or else the transactions will be canceled.
      • chargebackURL (mandatory): URL where the payment processor sends an HTTP POST message to whenever a refund associated with this transaction is done.
  • For a user to make a purchase, the Application must execute the Javascript method navigator.mozPay() with one or more signed payment requests (the JWTs). For example, the app might have a 'buy' button that triggers this method when clicked. Then navigator.mozPay method should take the signed payment JWT or an array of them. It will return a DOMRequest object that the developer can use to monitor the progress of the operation.

 var request = navigator.mozPay([signedJWT1, signedJWTn]);
 request.onsuccess = function () {
   // The payment buy flow completed without errors.
   // This does NOT mean the payment was successful.
   waitForServerPostback();
 }
 request.onerror = function (errorMsg) {
   console.log('navigator.mozPay() error: ' + this.error.name + ': ' + errorMsg);
 }

  • The navigator.mozPay method will open a payment request confirmation screen based on the received JWTs, so the user can confirm and choose the payment method that is more appropriate for him. B2G would only show the payment providers for the JWT typ values that are pre-registered in the User Agent. JWTs containing a typ value not registered in the UA would be considered as invalid.
  • Once the user selects a Payment Provider the UA will open a special window (a "chrome" dialog) containing the Payment Provider's buy flow that is registered in the User Agent for the typ value of the passed JWT (e.g. Firefox Marketplace in the example above).
  • Why an array of JWTs? The Developer will have a specific contract with each Payment Provider and will have a unique Application Secret for each provider. When a payment is initiated, the Application must send all JWTs. The user will select only one Payment Provider to complete the purchase. If there is only one JWT to choose from, the user will automatically use that Payment Provider, and this is the case for the first version of B2G.
  • The details of the exact buy flow are implemented by the Payment Provider. In Mozilla's pay flow, the user logs in via Persona, enters a PIN, and is presented with details about the item to be purchased.
  • The user confirms the purchase or cancels it.
  • If the user cancels or something goes wrong with the payment process, the flow returns to the DOMRequest.onerror callback.

Notifications

The Application must only rely on server side notifications to determine the outcome of a purchase. The Payment Provider will POST a confirmation message (a JWT) to the postbackURL (on success) or the chargebackURL (on error). The Application provides these URLs in the original JWT request. On a production server, these URLs must be HTTPS.

The POST request will have a content-type of application/x-www-form-urlencoded and the JWT will be in the notice form parameter. This JWT contains a copy of the original payment request plus a new response object that has a transactionID which identifies the Payment Provider's transaction.

When a JWT is received, the Application first needs to verify the signature using its Application Secret. If the signature is not valid, it probably was not sent from the Payment Provider and should be ignored. If the signtature is valid, then the application server should decode the JWT, record it, and respond with a 200 OK that contains the transactionID in plain text. If the Application server responds with an error status or does not respond with the right transactionID, the Payment Provider will consider this a failure. It will retry and/or notify the Developer about the failure. The Application must respond to the request in plain text containing just the transactionID value.

Postback

Here is an example of a JWT POSTed via the notice parameter to postbackURL that indicates a transaction was fully processed and was successful:

 {
   "iss": "marketplace.firefox.com",
   "aud": APPLICATION_KEY,
   "typ": "mozilla/payments/pay/postback/v1",
   "exp": 1337370900,
   "iat": 1337360900,
   "request": {
     "id": "915c07fc-87df-46e5-9513-45cb6e504e39",
     "pricePoint": 1,
     "name": "Magical Unicorn",
     "description": "Adventure Game item",
     "productData": "user_id=1234&my_session_id=XYZ",
     "postbackURL": "https://yourapp.com/payments/postback",
     "chargebackURL": "https://yourapp.com/payments/chargeback"
   },
   "response": {
     "transactionID": "webpay:84294ec6-7352-4dc7-90fd-3d3dd36377e9"
   }
 }

Here is an example response that includes just the transactionID:

 
 HTTP/1.1 200 OK
 Content-Type: text/plain
 
 webpay:84294ec6-7352-4dc7-90fd-3d3dd36377e9
 

Chargeback

Here is an example of a JWT POSTed via the notice parameter to chargebackURL that indicates a transaction was fully processed but was unsuccessful:

 {
   "iss": "marketplace.firefox.com",
   "aud": APPLICATION_KEY,
   "typ": "mozilla/payments/pay/chargeback/v1",
   "exp": 1337370900,
   "iat": 1337360900,
   "request": {
     "id": "915c07fc-87df-46e5-9513-45cb6e504e39",
     "pricePoint": 1,
     "name": "Magical Unicorn",
     "description": "Adventure Game item",
     "productData": "user_id=1234&my_session_id=XYZ",
     "postbackURL": "https://yourapp.com/payments/postback",
     "chargebackURL": "https://yourapp.com/payments/chargeback"
   },
   "response": {
     "transactionID": "webpay:84294ec6-7352-4dc7-90fd-3d3dd36377e9",
     "reason": "refund"
   }
 }

A chargeback JWT might be received instead of or in addition to a postback. The response will contain a reason attribute, as follows:

refund
The payment was refunded either upon request of the customer or by an administrator.
reversal
A buyer has asked the credit card issuer to reverse a transaction after it has been completed. The buyer might do this through the credit card company as part of a dispute.

Here is an example response that includes just the transactionID:

 
 HTTP/1.1 200 OK
 Content-Type: text/plain
 
 webpay:84294ec6-7352-4dc7-90fd-3d3dd36377e9
 

Refunds

Refunds are not yet supported by the navigator.mozPay API. At a future date, an Application may be able to request a refund like this:

 {
   "iss": APPLICATION_KEY,
   "aud": "marketplace.firefox.com",
   "typ": "mozilla/payments/refund/v1",
   "exp": 1337370900,
   "iat": 1337360900,
   "request": {
     "transactionID": "webpay:84294ec6-7352-4dc7-90fd-3d3dd36377e9",
     "reason": "User requested refund",
     "chargebackURL": "https://yourapp.com/payments/chargeback"
   }
 }

This would initiate a refund flow and POST a confirmation JWT when completed.

Payment Provider facing API

See the WebPaymentProvider spec for details on how to implement a payment provider for navigator.mozPay().

Testing

Firefox OS and the Firefox OS Simulator ship only with settings to make real payments. If you want to test payments, you can use the simulation feature described here. To test with a custom Payment Provider you need to adjust your settings.

Consult the B2G guide for how to set custom settings on a device.

Here is a helpful setting to disable HTTPS checks for testing:

 pref("dom.payment.skipHTTPSCheck", true);

Here is a rough example of how you could add a custom Payment Provider:

 pref("dom.payment.provider.1.name", "mockpayprovider");
 pref("dom.payment.provider.1.description", "Mock Payment Provider");
 pref("dom.payment.provider.1.type", "mock/payments/inapp/v1");
 pref("dom.payment.provider.1.uri", "https://mockpayprovider.phpfogapp.com/?req=");
 pref("dom.payment.provider.1.requestMethod", "GET");

Test apps

Current implementation

Current implementation

[1] The payment confirmation screen will not be shown if the navigator.mozPay call contains only one valid payment request (Bug 793811)

[2] Once the payment flow is loaded within the trusted UI the chrome code injects in the payment flow content the necessary functions to notify the platform about the successfull or failed purchase.

Modules

DOM part in Gecko

Contains the common code that exposes the navigator.mozPay function to the DOM and that is supposed to be shared by all the platforms (B2G, Fennec, Firefox).

This code lives in dom/payment.

B2G Glue part in Gecko

Contains the specific B2G code that triggers the trusted UI creation to embed the payment flow iframe and that injects the required paymentSuccess() and paymentFailed() functions within the payment flow content.

It implements the nsIPaymentUIGlue interface


interface nsIPaymentUIGlueCallback
{
   void onresult(in DOMString result);
};

interface nsIPaymentUIGlue
{
   // The 'paymentRequestsInfo' contains the payment request information
   // for each JWT provided via navigator.mozPay call.    
   void confirmPaymentRequest(in jsval paymentRequestsInfo,
                              in nsIPaymentUIGlueCallback successCb,
                              in nsIPaymentUIGlueCallback errorCb);

   void showPaymentFlow(in nsIPaymentFlowInfo paymentFlowInfo,
                        in nsIPaymentUIGlueCallback errorCb);
};

This code lives in b2g/components and b2g/chrome/content.

Gaia part

Contains the require code to create, open and close the trusted UI and the payment confirmation screen.

This code lives in the Gaia system app, specifically apps/system/js/payment.js and apps/system/js/popup_manager.js

Communication

The communication between Gecko and Gaia is done via mozChromeEvent and mozContentEvent, which is a communication mechanism between chrome and the Gaia system app. mozChromeEvent are sent from chrome to content and mozContentEvent are sent from content to crhome. Where "sent" is used loosely here, since "broascast" is probably a better term. This communication is required to control the creation and closure of the trusted UI and to inject the required functions to notify the success or failure of the payment flow within the payment provider content.

Helper Libraries

These are specific navigator.mozPay() libraries:

These are generic JWT libraries that you can use:

See also

Similar APIs