The freedom to transact and exchange value has become an important cornerstone of the Web. This specification outlines several browser-based mechanisms that make financial transactions easier to initiate while also making them more secure.

There are a number of ways that one may participate in the development of this specification:

Introduction

This API enables Web content to initiate payment or issue a refund for a product or service. Once implemented in the browser, an author may issue navigator.transact.pay() function to initiate a payment.

How to Read this Document

This document is a detailed specification for an application programming interface (API) for initiating payments from within a browser environment. The document is primarily intended for the following audiences:

WebPayments Architecture

An open web app will interact with a Payment Provider via navigator.transact.pay() 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

Detailed Payment Flow

Describe high-level payment flow, which is based on Mozilla's initial implementation for Firefox OS.

Definitions

Application
Open Web App which offers digital goods to be sold. OWAs charge users via navigator.transact.pay() 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 [[!JWT]] and must be securely protected on a web server.
Developer
Application developer, seller of digital goods.
Marketplace
Developer portal and application repository. Developers can submit apps to the Marketplace so users can purchase and download the apps. The Marketplace uses the navigator.transact.pay() function to charge users for application purchases.
Payment Provider
A client/server web application that serves content in a special iframe controlled by navigator.transact.pay(). 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
A tool that is used to interact with the Web. This can be a web browser, mobile phone operating system, or similar software system.

Sign-in

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

Specify user identity management, e.g. Persona.

Developer Registration

This is an implementation detail of the Payment Provider. The navigator.transact.pay() API facilities two parties in making a transaction: 1) a Developer and 2) a Payment Provider. It does not facilitate any part of the registration process.

A developer who creates a JSON Web Token [[!JWT]] 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

  1. The User opens an Application.
  2. 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.
  3. The user clicks a Buy Unicorn button.
  4. 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.
  5. The Application bubbles up the JWT to the client and calls navigator.transact.pay([theJWT]). This begins the hosted buy flow within a special window on the User Agent.
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"
   }
 }, 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. Marketplace).
typ (mandatory)
The JWT type. This identifies the payment provider, e.g. 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)
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.
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 provider 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 provider 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.transact.pay() 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.transact.pay() 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.transact.pay([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.transact.pay() error: ' + this.error.name + ': ' + errorMsg);
}
      
  1. The navigator.transact.pay 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. The User Agent 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.
  2. 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.
  3. 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.
  4. 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.
  5. The user confirms the purchase or cancels it.
  6. 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"
   },
   "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"
  },
  "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.transact.pay() 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

Figure out how to reference the WebPaymentProvider spec for details on how to implement a payment provider for navigator.transact.pay(). It may be that we need to fold that into this spec.

The Application Programming Interface

This API provides a clean mechanism that enables developers to initiate payments in a User Agent. A conformant Payment Initator MUST implement the entirety of the following API.

NavigatorTransactions

Navigator Transactions is the name of the high-level programming interface that Web developers use to initiate payments. If MUST be made available via the navigator.transact object.

DOMRequest pay()

Initiates a payment or refund given an array of JSON Web Tokens [[!JWT]] describing the types of actions to perform.

Should we break refunds out into their own method call? The downside of doing that is that the API is no longer fairly generic. However, .pay() doesn't really imply "refund", unless you think of a refund like a reverse payment? The other thing we could do is use .process() or .transact() as the method call and change the interface name to something like navigator.funds.transact() or navigator.wallet.transact().

Each request object will contain a typ key. The value associated with this key will be used to perform matchmaking between the customer's list of registered payment providers and the list of preferred payment providers for the Merchant. Typically, a User Agent will request that the customer rank their registered payment providers in most preferable to least preferable order. If this ranking is performed, a payment provider can be selected automatically by picking highest ranked payment provider in the customer's payment provider list with a payment provider that also exists in the Merchant's payment provider list.

Should the merchant provide a payment provider registration link as an option to the call in the case where there is no match between the list of payment providers accepted by the merchant vs. the list of registered providers for the customer?

object[] requests
An array of JSON Web Tokens [[!JWT]] describing the type of payment operations to perform.
DOMRequest registerProvider()

Registers a payment provider with the browser such that future transactions may provide an interface to the user to select which payment provider that they would like to use to complete a transaction.

Registering a payment provider can alter the choices given to the User when a transaction is processed. In the worst case, an attacker could register a fake payment provider that collects all of the User's credit card or banking details. In order to ensure that the User is protected from this sort of attack, the User Agent MUST provide a special interface that is not easily spoofed by an attacker.

When a User Agent executes this method, it must ensure that the following checks are performed:

  1. The call must be done on a page served over TLS and with no scripts loaded from a non-secure channel.
  2. The domain for the id and services URLs must match the domain for the page requesting the registration, etc.
object provider
An object consisting of the following keys and values:
id
A URL identifier for the provider. This URL should be the base domain for the payment provider, for example: https://example.org/. Ideally, this URL could be modified by appending a relative path, such as .well-known/ to discover more services related to the payment provider.
name (optional)
A display name for the provider. If one is not provided, the domain name of the id SHOULD be used.
icon (optional)
A display icon for the provider. If one is not provided, the favicon.ico file associated with the id SHOULD be used.
transactionType

A free-form string, or array of strings, advertising the type of transactions supported by the payment provider. Other specifications, such as [[WEB-PAYMENTS]], outline the acceptable values for this parameter. This parameter is used by the pay() method to determine which registered payment provider is capable of processing a particular transaction type (for example: credit cards, mobile phone carrier-based billing, or PaySwarm).

services (optional)
A URL that can be used to determine more information about the payment provider Web service endpoints by the User Agent or Merchant.
DOMRequest getProviders()

Retrieves all of the payment providers that are registered with the User Agent that match the query criteria given and were authorized to be included in the response by the User.

It is not always preferable for a User to have all of their payment providers exposed to a Merchant. In the worst case, an attacker can use this information to execute spear-fishing attacks against the User. The User Agent should provide the User with an interface that allows them to select which providers they want to expose to the merchant. In many cases, the User will only want to expose one, which will make the purchase process proceed more fluidly.

The result will be an array of payment providers that are in the same format as the objects passed to the registerProvider() method.

string[] query
An array of payment provider types that are supported by the merchant.

Acknowledgements

The authors would like to thank ...

Thanks to the following individuals, in order of their first name, for their input on the specification: ...