WebAPI/WebPayment

From MozillaWiki
Jump to: navigation, search

WebPayment API Specification (DRAFT)

Note: This API is in the process of being removed from Android and Desktop, long term status in B2G is unclear.

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",
      "icons": {
        "64": "https://yourapp.com/img/icon-64.png",
        "128": "https://yourapp.com/img/icon-128.png"
      },
      "productData": "user_id=1234&my_session_id=XYZ",
      "postbackURL": "https://yourapp.com/payments/postback",
      "chargebackURL": "https://yourapp.com/payments/chargeback",
      "defaultLocale": "en",
      "locales": {
        "de": {
          "name": "Magisches Einhorn",
          "description": "Adventure Game Artikel"
        }
      }
    }
  }, 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.
    • 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.
    • 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.
    • icons (optional): A map of icon URLs for the product you are selling. The keys are width/height pixel values (images must be square). The Payment Provider will use an image at the appropriate size on the payment confirmation page. For details about how Mozilla's Payment Provider handles icons, see the payments guide.
    • 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.
    • defaultLocale (mandatory if locales is defined, otherwise it is optional): Describes what language the name and description are in.
    • locales (optional): is a map of one or more locale-specific overrides of the data contained in the in-app product, which UIs use to provide localized views based on the accessing device's locale. For example, if you have Italian readers installing your app, you probably want to give them Italian UI text. Each locale entry is keyed on a language tag (RFC 4646) and contains the keys you want to replace. You can only override name and description.
  • 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.

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",
      "icons": {
        "64": "https://yourapp.com/img/icon-64.png",
        "128": "https://yourapp.com/img/icon-128.png"
      },
      "productData": "user_id=1234&my_session_id=XYZ",
      "postbackURL": "https://yourapp.com/payments/postback",
      "chargebackURL": "https://yourapp.com/payments/chargeback",
      "defaultLocale": "en",
      "locales": {
        "de": {
          "name": "Magisches Einhorn",
          "description": "Adventure Game Artikel"
        }
      }
    },
    "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",
      "icons": {
        "64": "https://yourapp.com/img/icon-64.png",
        "128": "https://yourapp.com/img/icon-128.png"
      },
      "productData": "user_id=1234&my_session_id=XYZ",
      "postbackURL": "https://yourapp.com/payments/postback",
      "chargebackURL": "https://yourapp.com/payments/chargeback",
      "defaultLocale": "en",
      "locales": {
        "de": {
          "name": "Magisches Einhorn",
          "description": "Adventure Game Artikel"
        }
      }
    },
    "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