The Payment Request API [[!PAYMENT-REQUEST-API]] provides a standard way to initiate payment requests from Web pages and applications. User agents implementing that API prompt the user to select a way to handle the payment request, after which the user agent returns a payment response to the originating site. This specification adds payment apps to that user experience. It defines how users register payment apps with user agents, how user agents support the display of information about payment options the user can select to handle the payment request, how the user selects a payment app, and how communication takes place between user agents and payment apps to fulfill the requirements of the underlying Payment Request API.
The Web Payments Working Group maintains a list of all bug reports that the group has not yet addressed. This draft highlights some of the pending issues that are still to be discussed in the working group. No decision has been taken on the outcome of these issues including whether they are valid. Pull requests with proposed specification text for outstanding issues are strongly encouraged.
The Web Payments Working Group seeks to streamline payments on the Web to help reduce payment "abandonment" and make it easier to use new payment methods on the Web. It has published the Payment Request API [[!PAYMENT-REQUEST-API]] as a standard way to initiate payment requests from Web pages and applications. This specification adds payment apps to that user experience. A payment app is software that enables the user to fulfill a payment request using the user's preferred payment method.
This specification defines:
Payment apps may be implemented in a variety of ways: as Web applications, native operating system applications, user agent extensions, built-in user agent components, interface-less Web services, or a combination. This specification does not cover every aspect of communication on every platform.
The Web Payments Working Group has used the term "mediator" to refer to the software (here, the user agent) that carries out the activities defined in this specification (matching, information display, etc.).
This specification defines one class of products:
A user agent MUST behave as described in this specification in order to be considered conformant. In this specification, user agent means a Web browser or other interactive user agent as defined in [[!HTML5]].
User agents MAY implement algorithms given in this specification in any way desired, so long as the end result is indistinguishable from the result that would be obtained by the specification's algorithms.
A conforming Payment App API user agent MUST also be a conforming implementation of the IDL fragments of this specification, as described in the “Web IDL” specification. [[!WEBIDL]]
This specification relies on several other underlying specifications.
DOMException and the following DOMException types from [[!DOM4]] are used:
Type | Message (optional) |
---|---|
AbortError |
The operation was aborted |
InvalidStateError |
The object is in an invalid state |
SecurityError |
The operation is only supported in a secure context |
OperationError |
The operation failed for an operation-specific reason. |
The following DOMException types from [[!WEBIDL]] are used:
Type | Message (optional) |
---|---|
NotAllowedError |
The request is not allowed by the user agent or the platform in the current context. |
This section (which may be temporary) intends to help build shared understanding of the capabilities and limitations of the specified model.
The user agent may offer features to facilitate selection (e.g., launch a chosen payment app or option every time the user wants to pay at a given Web site); those features lie outside the scope of this specification.
Updates needed for payment method option definition.
WindowClient
used by user agent-based
payment apps to interact with the user when doing so is necessary to
complete the payment transaction.
The Web Payments Working Group intends for this specification to apply to any payment app that may be invoked by the user agent, whatever technologies have been used to implement the payment app.
The Working Group has not yet agreed that the system should support recommended payment apps. Inclusion might involve small changes to payment request API. The group has also discussed the idea of user agent-recommended payment apps, for example, when the user agent is aware of an app for a proprietary payment method.
We need a clearer introduction to the concept of a payment option, and how it relates to payment apps.
The difference between supported and enabled payment methods is one of design-time vs runtime consideration. A payment app supports all the payment methods it was designed to support; however at runtime only a subset may be enabled due to configuration or other runtime requirements that may not have been met for all supported payment methods.
The data passed between the user agent and the payment app will be serialized as JSON data.
In this specification we use service workers to connect user agents with user agent-based payment apps. We do so for several reasons:
The use of service workers restricts user agent-based payment apps so that they must run only in secure contexts. The introduction of this restriction is deliberate, due to the sensitivity of the role that payment apps play.
Here is the flow envisioned by this document:
payment request API
is called, the user agent
displays a list of registered service workers associated
with matching payment methods (along with any other payment apps
that may be available to the user agent).Promise<PaymentResponse>
returned from
PaymentRequest.show()
to resolve.
The Service Worker specification defines a ServiceWorkerRegistration
interface
[[!SERVICE-WORKERS]], which this specification extends.
partial interface ServiceWorkerRegistration { readonly attribute PaymentAppManager paymentAppManager; };
interface PaymentAppManager { Promise<void> setManifest(PaymentAppManifest manifest); Promise<PaymentAppManifest> getManifest (); };
The setManifest
method is used to enable a
service worker to process payment requests, and to set the
properties associated with the payment app.
The following algorithm provides an extension point: other specifications that add new members to the manifest are encouraged to hook themselves into this specification at this point in the algorithm.
The setManifest
method, when invoked,
MUST run the following steps or their equivalent:
DOMException
whose name is
"SecurityError
" and terminate these steps.
DOMException
whose name is "OperationError
" and terminate
these steps.
manifest
argument.
PaymentAppManager
's associated service
worker registration.
DOMException
whose
name is "InvalidStateError
" and terminate these steps.
DOMException
whose name is
"InvalidStateError
" and terminate these steps.
DOMException
whose
name is "NotAllowedError
" and terminate these steps.
DOMException
whose name is
"NotAllowedError
" and terminate these steps.
label
and
icon
set with the payment app for user reference.
options
field of the manifest:
label
and icon
fields.
enabledMethods
field, associate the payment option and the payment app
with the payment method for future use.
undefined
.
The getManifest
method is used to
retrieve the properties associated with a registered
payment app.
The getManifest
method, when invoked, MUST run the
following steps or their equivalent:
DOMException
whose name is
"SecurityError
" and terminate these steps.
AbortError
"
and terminate these steps.
dictionary PaymentAppManifest { DOMString label; DOMString? icon; sequence<PaymentAppOption> options; };
label
memberlabel
member is a string that represents the
label for this payment app as it is usually displayed
to the user.
icon
membericon
member defines an icon for this
payment app as it is usually displayed to the user.
options
memberoptions
member lists the payment method identifiers of the
payment methods enabled by this option.
Options are an extra layer of abstraction, because they allow flattening of payment apps. The flattening may result in unique UX challenges. For example, if two payment apps both have "Visa ending in ***4756" payment option, then users may be confused when they see two such labels in UI. One solution is to prepend the payment app name, e.g., "ExampleApp Visa ending in ***4756". However, when only one app is installed, the text "ExampleApp" is redundant.
It may be simpler for implementers to remove payment options from this spec. A medium level of complexity is to specify payment options, but give user agents choice of whether payment options are supported.
dictionary PaymentAppOption { DOMString label; DOMString? icon; DOMString id; sequence<DOMString> enabledMethods; };
label
memberlabel
member is a string that represents the
label for this option as it is usually displayed to the user
when selecting a payment app.
icon
memberid
memberid
member is an identifier, unique
within the PaymentAppManifest, that will be passed to the
payment app to indicate which PaymentAppOption the user
selected.
enabledMethods
memberenabledMethods
member lists the payment method identifiers of the
payment methods enabled by this option.
Aside from ServiceWorker
registration, it's useful for user agents to
download the payment app manifest from a well defined location. This allows
for merchants to recommend payment apps via the URL of the payment app.
https://bobpay.com
to
https://bobpay.com/payment-manifest.json
?
The following example shows how to register a user agent-based payment app:
navigator.serviceWorker.register('/exampleapp.js') .then(function(registration) { return registration.paymentAppManager.setManifest({ label: "ExampleApp", icon: "...", options: [ { label: "Visa ending ****4756", icon: "...", id: "dc2de27a-ca5e-4fbd-883e-b6ded6c69d4f", enabledMethods: ["basic-card#visa"] }, { label: "My Bob Pay Account: john@example.com", icon: "...", id: "c8126178-3bba-4d09-8f00-0771bcfd3b11", enabledMethods: ["https://bobpay.com/"] }, { label: "Add new credit/debit card to ExampleApp", icon: "...", id: "new-card", enabledMethods: [ "basic-card#visa", "basic-card#mastercard", "basic-card#amex" } ] }); }).then(function() { console.log("Installed payment app from /paymentapp.js"); // Success! }).catch(function(error) { console.log(error); });
The Editors will update the payment method identifier syntax in this and other examples to align with [[!METHOD-IDENTIFIERS]], once a final format has been agreed upon.
Information required for payment apps should be present in the payment
app manifest under an extension point. For example,
information for Android native payment apps may live under
"android"
section of the app manifest. This specification does
not define the contents of native app descriptions. This is defined
elsewhere.
What else, if anything, should we say about registering native payment apps?
Native payment apps on some platforms (e.g., on Android) can claim ownership of their origins. To verify origin ownership, user agents need to perform extra steps that are not defined in this specification.
We anticipate that [[!METHOD-IDENTIFIERS]] will define the PMI matching algorithm. This specification will explain how to invoke that algorithm using data available from the Payment Request API input and payment method information aggregated from:
After matching the user agent will have a list of payment options that the user can select to handle the payment request. How will these be ordered when they are displayed to the user, where do recommended apps fit in to the order and how do we treat apps that are both registered and recommended?
What information is needed by the user agent to display selectable apps/options? This needs to be captured during registration.
The output of the payment method matching algorithm will be a list of matching payment options from registered payment apps and a list of recommended payment apps. The user agent will present this list of options to the user so they can select how they want to handle the payment request.
We have identified a number of user experiences that we would like to harmonize. Just a few examples here:
The following are examples of user agent display behavior.
Once the user has selected a payment option, the user agent is responsible for preparing payment app request data, invoking the payment app, providing the request data to the payment app, and returning the payment app response through the Payment Request API.
dictionary PaymentAppRequestData { DOMString origin; sequence<PaymentMethodData> methodData; PaymentItem total; sequence<PaymentDetailsModifier> modifiers; DOMString optionId; };
origin
attributemethodData
attributePaymentMethodData
dictionaries containing the payment method identifiers for the
payment methods that the web site accepts and any associated
payment method specific data.
It is populated from the
PaymentRequest using the Method Data Population Algorithm
defined below.
total
attributetotal
field of the PaymentDetails
provided
when the corresponding PaymentRequest object was instantiated.
modifiers
attributePaymentDetailsModifier
dictionaries
contains modifiers for particular payment method identifiers (e.g.,
if the payment amount or currency type varies based on a
per-payment-method basis). It is populated from the
PaymentRequest using the Modifiers Population Algorithm
defined below.
optionId
attributeid
field provided during
payment app registration.
To initialize the value of the methodData
, the user agent
MUST perform the following steps or their equivalent:
enabledMethods
to
registeredMethods.
Sequence
.
Sequence
.
PaymentRequest
@[[\methodData]] in the
corresponding payment request, perform the following steps:
supportedMethods
and registeredMethods.
PaymentMethodData
object.
PaymentMethodData
.
supportedMethods
to
a list containing the members of commonMethods.
data
.
methodData
to dataList.
To initialize the value of the modifiers
, the user agent
MUST perform the following steps or their equivalent:
enabledMethods
to
registeredMethods.
Sequence
.
Sequence
.
PaymentRequest
@[[\paymentDetails]].modifiers
in the corresponding payment request, perform the following steps:
supportedMethods
and registeredMethods.
PaymentDetailsModifier
object.
PaymentDetailsModifier
.
supportedMethods
to
a list containing the members of commonMethods.
total
to a structured
clone of inModifier.total
.
additionalDisplayItems
to a structured clone of
inModifier.additionalDisplayItems
.
modifiers
to modifierList.
Payment apps are invoked when a payee requests a payment
by calling PaymentRequest.show()
and the user selects a
payment app (or has one implicitly selected by previously established
user preferences). If the user selects a user agent-based payment
app to service the request, the service worker corresponding
to that application receives an event with the
PaymentAppRequestData containing information about the payment
being requested. The event also contains a function that allows the
payment app to provide a payment response back to the
payee. This process is formally described in the following
sections.
ServiceWorkerGlobalScope
The Service Worker specification defines a
ServiceWorkerGlobalScope
interface [[!SERVICE-WORKERS]],
which this specification extends.
partial interface ServiceWorkerGlobalScope { attribute EventHandler onpaymentrequest; };
onpaymentrequest
attributeonpaymentrequest
attribute is an event handler
whose corresponding event handler event type is
paymentrequest
.
The PaymentRequestEvent interface represents a received payment request.
paymentrequest
Event
The PaymentRequestEvent represents a received payment request.
[Exposed=ServiceWorker] interface PaymentRequestEvent : ExtendableEvent { readonly attribute PaymentAppRequestData data; void respondWith((Promise<PaymentResponse> or PaymentResponse) r); };
data
attributerespondWith
method
Upon receiving a payment request by way of
PaymentRequest.show()
and subsequent user selection of a
user agent-based payment app, the user agent MUST run
the following steps or their equivalent:
PaymentRequest.show()
with a
DOMException whose value "InvalidStateError" and
terminate these steps.
PaymentRequestEvent
interface, with the
event type paymentrequest
, which does not bubble,
cannot be canceled, and has no default action. data
attribute of
e to a new PaymentAppRequestData instance,
populated as described in
.
PaymentRequest.show()
with a
DOMException whose value "OperationError".
Payment Apps that require user input can open a payment window using the
clients.openWindow()
method defined in [[!SERVICE-WORKERS]].
Absent user preferences that override such behavior, user interaction is
required during payment requests, in the form of payment app selection. As
a consequence, the user agent MUST treat a paymentrequest event as
user interaction for the purposes of determining whether the service
worker is allowed to open a window.
The actual rendering of a payment app window is a user agent implementation detail. While opening an entirely new window is possible, it is more likely that the contents will be rendered in a way that makes it more obvious that the interactions pertain to the payment transaction. This is an area for potential user agent experimentation and differentiation. The opening of a payment app window versus other types of windows can be distinguished based on the event type the user agent is using to grant permission to open a window.
The remainder of this section is a non-normative explanation of how
the service worker WindowClient
class can be used to interact
with users.
Upon calling clients.openWindow(), the payment app receives a
Promise which resolves to a WindowClient
. For the
purposes of this discussion, we will refer to this
WindowClient
as client. The payment app can use
the client.postMessage()
method to send messages
to the payment app window.
When a payment app window receives the message
event
from the payment app, this event will contain a source
attribute which indicates the payment app's service worker. The payment
app window can then call source.postMessage()
to send a
response to the payment app. Once the payment app window has complete its
interaction with the user, it closes the window and uses this
postMessage()
call to return information to the payment app.
In order for this approach to work, we have to treat a paymentrequest as permission to open a popup, which is a formal property relied up on by [[!SERVICE-WORKERS]]. We need to be careful that this does not become an end-run around exiting pop-up protections.
Do we want to define a new FrameType
for payment app
windows? This requires input from someone with detailed knowledge of
service worker design.
The user agent receives a successful response from the payment app
through resolution of the Promise provided to the respondWith
function of the corresponding PaymentRequestEvent dictionary.
The application is expected to resolve the Promise with a
PaymentResponse
dictionary instance containing the payment
response information.
When this Promise is resolved, the user agent MUST run the user accepts the payment request algorithm as defined in [[!PAYMENT-REQUEST-API]], replacing steps 6 and 7 with these steps or their equivalent:
PaymentResponse
used to
resolve the PaymentRequestEvent.respondWith
Promise.
methodName
is not present or
not set to one of the values from
PaymentRequestEvent.data
, reject the Promise
created by PaymentRequest.show()
with DOMException
whose value "InvalidStateError" and terminate these steps.
methodName
and assign it to
response.methodName
.
details
is not present,
reject the Promise created by
PaymentRequest.show()
with a DOMException whose
value is "InvalidStateError" and terminate these steps.
details
and assign it to
response.details
.
The user agent receives a failure response from the payment app through
rejection of the Promise. The user agent MUST use the rejection reason
to reject the Promise that was created by
PaymentRequest.show()
.
The following example shows how to respond to a payment request:
paymentRequestEvent.respondWith(new Promise(function(accept,reject) { /* ... processing may occur here ... */ accept({ methodName: "basic-card#visa", details: { card_number : "1232343451234", expiry_month : "12", expiry_year : "2020", cvv : "123" } }); });
Some payment methods might require a back channel to guarantee payment response delivery (especially push payment methods). Should it be part of the generic portion of paymentRequest and paymentResponse? [Ed Note: the "complete()" attribute of the "PaymentResponse" interface would serve this purpose quite cleanly.]
This example codes shows how to use this API via a scheme in
which a POST
is sent to a URL with the payment request as a
body. The response is allowed to be either application/json
(which is inferred to contain a payment response), or
text/html
(which contains content to be rendered to the
user).
var contentType; var paymentPromise; /* Handle payment request from a payee */ self.addEventListener('paymentrequest', function(e) { paymentPromise = new Promise(function(accept, reject) { fetch("https://www.example.com/bobpay/process", { method: "POST", body: JSON.stringify(e.data) }) .then(function(response) { contentType = response.headers.get("content-type"); if (!contentType) { throw new Error("No content type header"); } return response.text(); }).then(function(body) { if(contentType.indexOf("application/json") !== -1) { /* Respond to the payment request with the received body */ accept(JSON.parse(body)); } else if (contentType.indexOf("text/html") !== -1) { { /* Open a new payment window and populate it with the document returned from the response */ var url = "data:text/html;base64," + btoa(body); clients.openWindow(url).then(function(windowClient) { windowClient.postMessage(e.data); }); } else { throw new Error("Unexpected value in content type header"); } }).catch(function(err) { reject(err); }); e.respondWith(paymentPromise); }); /* Handle payment response from a payment app window */ self.addEventListener('message', function(e) { if (e.data.hasOwnProperty('name')) { paymentPromise.reject(e.data); } else { paymentPromise.resolve(e.data); } });
Using the simple scheme described above, a trivial HTML page that is loaded into the payment app window to implement the basic card scheme might look like the following:
<html> <body> <form id="form"> <table> <tr><th>Card Number:</th><td><input name="card_number"></td></tr> <tr><th>Expiration Month:</th><td><input name="expiry_month"></td></tr> <tr><th>Expiration Year:</th><td><input name="expiry_year"></td></tr> <tr><th>CVV:</th><td><input name="cvv"></td></tr> <tr><th></th><td><input type="submit" value="Pay"></td></tr> </table> </form> <script> window.addEventListener("message", function(e) { var form = document.getElementById("form"); /* Note: message sent from payment app is available in e.data */ form.onsubmit = function() { var details = {}; ["card_number","expiry_month","expiry_year","cvv"].forEach(function(field) { details[field] = form.elements[field].value; }); e.source.postMessage({ methodName: "basic-card#visa", details: details }); window.close(); } }); </script> </body> </html>