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 defines capabilities that enable Web applications to handle payment requests.
We have changed the title of this specification but left the identifier as-is. We are likely to want to assign it a new URL prior to FPWD.
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 "shopping cart abandonment" and make it easier to deploy 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 E-Commerce Web sites and applications.
A payment app is a Web application that manages payment requests on behalf of the user by supporting one or more payment methods. To enable a Web application to handle payment requests, this specification defines:
This specification does not address how software built with operating-system specific mechanisms (e.g., "native mobile apps") handle payment requests.
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 Handler 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 |
NotFoundError |
The object can not be found here. |
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. |
In this document we envision the following flow:
This specification does not address activities outside this flow, including:
Thus, an origin will rely on many other Web technologies defined elsewhere for lifecycle management, security, user authentication, user interaction, and so on.
The Service Worker specification defines a
ServiceWorkerRegistration
interface
[[!SERVICE-WORKERS]], which this specification extends.
partial interface ServiceWorkerRegistration { readonly attribute PaymentManager paymentManager; };
interface PaymentManager { attribute PaymentInstruments instruments; attribute PaymentWallets wallets; };
instruments
attributewallets
attributeinterface PaymentInstruments { Promise<boolean> delete(DOMString instrumentKey); Promise<PaymentInstrument> get(DOMString instrumentKey); Promise<sequence<DOMString>> keys(); Promise<boolean> has(DOMString instrumentKey); Promise<void> set(DOMString instrumentKey, PaymentInstrument details); };
The PaymentInstruments interface represents a collection of payment instruments, each uniquely identified by an instrumentKey. The instrumentKey identifier will be passed to the payment handler to indicate the PaymentInstrument selected by the user.
delete
methodinstrumentKey
, remove it from
the collection and resolve p with true.get
methodinstrumentKey
, resolve p
with that PaymentInstrument.
keys
methodinstrumentKey
s for the PaymentInstruments
contained in the collection.
has
methodinstrumentKey
, resolve p
with true.set
methodinstrumentKey
, replace it with
the PaymentInstrument in details
.
details
as a new member of the collection and associate it with the
key instrumentKey
.
return paymentManager.instruments.keys().then((keys) => { Promise.all(keys.map(paymentManager.instruments.delete(key))) });
dictionary PaymentInstrument { required DOMString name; sequence<ImageObjects> icons; sequence<DOMString> enabledMethods; object capabilities; };
name
membername
member is a string that
represents the label for this payment Instrument as it is
usually displayed to the user.icons
membericons
member is an array of image
objects that can serve as iconic representations of the
payment Instrument when presented to the user for
selection.enabledMethods
memberenabledMethods
member lists the payment method identifiers of the
payment methods enabled by this Instrument.
capabilities
membercapabilities
member contains a list of payment-method-specific
capabilities that this payment handler is capable of
supporting for this Instrument. For example, for the
basic-card
payment method, this object will
consist of an object with two fields: one for
supportedNetworks
, and another for
supportedTypes
.interface PaymentWallets { Promise<boolean> delete(DOMString walletKey); Promise<WalletDetails> get(DOMString walletKey); Promise<sequence<DOMString>> keys(); Promise<boolean> has(DOMString walletKey); Promise<void> set(DOMString walletKey, WalletDetails details); };
Where it appears, The walletKey
parameter is
a unique identifier for the wallet.
delete
methodwalletKey
, remove it from
the collection and resolve p with true.get
methodwalletKey
, resolve p
with that WalletDetails.
keys
methodwalletKey
s for the WalletDetailss
contained in the collection.
has
methodwalletKey
, resolve p
with true.set
methodwalletKey
, replace it with
the WalletDetails in details
.
details
as a new member of the collection and associate it with the
key walletKey
.
dictionary WalletDetails { required DOMString name; sequence<ImageObject> icons; required sequence<DOMString> instrumentKeys; };
name
membername
member is a string that
represents the label for this wallet as it is usually
displayed to the user.icons
membericons
member is an array of image
objects that can serve as iconic representations of the
wallet when presented to the user for selection.instrumentKeys
memberinstrumentKeys
member is a
list of instrumentKey
s from
PaymentManager.instruments
,
indicating which PaymentInstrument objects are associated
with this Wallet, and should be displayed as being "contained in"
the wallet. While it is not generally good practice, there is no
restriction that prevents a PaymentInstrument from appearing in
more than one Wallet.
The following example shows how to register a payment handler:
Issue 94. The means for code requesting permission to handle
payments is not yet defined. The code below is based on one
potential model (a requestPermission()
method on the
PaymentManager),
but this is likely to change.
window.addEventListerner("DOMContentLoaded", async() => { const { registration } = await navigator.serviceWorker.register('/sw.js'); if (!paymentManager) { return; // not supported, so bail out. } const state = await navigator.permissions.query({ name: "paymenthandler" }); switch (state) { case "denied": return; case "prompt": // Note -- it's not clear how this should work yet; see Issue 94. const result = await registration.paymentManager.requestPermission(); if (result === "denied") { return; } break; } // Excellent, we got it! Let's now set up the user's cards. await addInstruments(registration); }, { once: true }); function addInstruments(registration) { const instrumentPromises = [ registration.paymentManager.instruments.set( "dc2de27a-ca5e-4fbd-883e-b6ded6c69d4f", { name: "Visa ending ****4756", enabledMethods: ["basic-card"], capabilities: { supportedNetworks: ['visa'], supportedTypes: ['credit'] } }), registration.paymentManager.instruments.set( "c8126178-3bba-4d09-8f00-0771bcfd3b11", { name: "My Bob Pay Account: john@example.com", enabledMethods: ["https://bobpay.com/"] }), registration.paymentManager.instruments.set( "new-card", { name: "Add new credit/debit card to ExampleApp", enabledMethods: ["basic-card"], capabilities: { supportedNetworks: ['visa','mastercard','amex','discover'], supportedTypes: ['credit','debit','prepaid'] } }), ]; return Promise.all(instrumentPromises).then(() => { registration.paymentManager.wallets.set( "12a1b7e5-16c0-4c09-a312-9b191d08517b", { name: "Acme Bank Personal Accounts", icons: [ { src: "icon/lowres.webp", sizes: "48x48", type: "image/webp" }, { src: "icon/lowres", sizes: "48x48" } ], instrumentKeys: [ "dc2de27a-ca5e-4fbd-883e-b6ded6c69d4f", "c8126178-3bba-4d09-8f00-0771bcfd3b11", "new-card" ] }); }); };
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.
After applying the matching algorithm defined in Payment Request API, the user agent displays a list of matching origins for the user to make a selection. This specification includes a limited number of display requirements; most user experience details are left to user agents.
The following are examples of payment handler ordering:
The user agent MUST enable the user to select any displayed Instrument.
Issue 98. There has been pushback to always requiring display of instruments (e.g., on a mobile devices). User agents can incrementally show instruments. Or user agents can return an empty Instrument ID and it becomes the payment app's responsibility to display instruments to the user.
At times, the same origin may wish to group instruments with greater flexibility and granularity than merely "by origin." These use cases include:
A Wallet is a grouping of Instruments for display purposes.
To enable developers to build payment apps in a variety of ways, we decouple the registration (and subsequent display) of Instruments from how payment handlers respond to paymentrequest events. However, the user agent is responsible for communicating the user's selection in the event.
Users agents may wish to enable the user to select individual displayed Instruments. The payment handler would receive information about the selected Instrument and could take action, potentially eliminating an extra click (first open the payment app then select the Instrument).
Issue 98. Should we require that, if displayed, individual Instruments must be selectable? Or should we allow flexibility that Instruments may be displayed, but selecting any one invokes all registered payment handlers? One idea that has been suggested: the user agent (e.g., on a mobile device) could first display the app-level icon/logo. Upon selection, the user agent could display the Instruments in a submenu.
Once the user has selected an Instrument, the user agent fires a paymentrequest event with a Payment App Request, and uses the subsequent Payment App Response to create a PaymentReponse for [[!PAYMENT-REQUEST-API]].
interface PaymentAppRequest { readonly attribute DOMString origin; readonly attribute DOMString id; readonly attribute FrozenArray<PaymentMethodData> methodData; readonly attribute PaymentItem total; readonly attribute FrozenArray<PaymentDetailsModifier> modifiers; readonly attribute DOMString instrumentId; };
origin
attributeid
attributeid
attribute returns this the
[[\details]].id
from the PaymentRequest that
corresponds to this PaymentAppRequest
.
methodData
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.
displayItems
displayItems
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.
instrumentId
attributeid
field provided during registration.
To initialize the value of the methodData
,
the user agent MUST perform the following steps or their
equivalent:
PaymentManager.instruments
, add all entries
in instrument.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:
PaymentManager.instruments
, add all entries
in instrument.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.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
EventThe PaymentRequestEvent represents a received payment request.
[Exposed=ServiceWorker] interface PaymentRequestEvent : ExtendableEvent { readonly attribute PaymentAppRequest appRequest; void respondWith(Promise<PaymentAppResponse>appResponse); };
appRequest
attributerespondWith
methodUpon receiving a payment request by way of
PaymentRequest.show()
and subsequent user
selection of a payment handler, 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.
appRequest
attribute of
e to a new PaymentAppRequest
instance, populated as described in .
PaymentRequest.show()
with a
DOMException whose value
"OperationError".
An invoked payment handler may or may not need to display information about itself or request user input. Some examples of potential payment handler displays include:
See issue 97 for discussion about opening window in a way that is consistent with payment flow and not confusing to the user. For example, there is consensus that in a desktop environment, a payment handler should not open a window in a new browser tab, as this is too dissociated from the checkout context.
User agents SHOULD display the origin of a running payment handler to the user.
dictionary PaymentAppResponse { DOMString methodName; object details; };
methodName
attributedetails
attributeThe user agent receives a successful response from the
payment handler 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
PaymentAppResponse
instance containing the
payment response. In case of user cancellation or error,
the application may signal failure by rejecting the
Promise.
If the Promise is rejected, the user agent MUST run the payment app failure algorithm. The exact details of this algorithm is left for user agent implementers to decide on. Acceptable behaviors include, but are not limited to:
PaymentRequest.show()
.If this Promise is successfully 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:
PaymentAppResponse
instance used to
resolve the
PaymentRequestEvent.respondWith
Promise.
methodName
is not present or not set to one of the values from
PaymentRequestEvent.appRequest
, run
the payment app failure algorithm and
terminate these steps.
methodName
and
assign it to
response.methodName
.
details
is
not present, run the payment app failure
algorithm and terminate these steps.
details
and assign
it to response.details
.
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", details: { cardHolderName: "John Smith", cardNumber: "1232343451234", expiryMonth: "12", expiryYear : "2020", cardSecurityCode: "123" } }); });
[[!PAYMENT-REQUEST-API]] defines a
paymentRequestID
that parties in the
ecosystem (including payment app providers and payees)
may use for reconciliation after network or other
failures.
paymentrequest
eventThis example shows how to write a service worker that listens to the paymentrequest event. When a payment request event is received, the service worker opens a window in order to interact with the user.
Per
issue 97, we expect not to use
clients.OpenWindow()
and instead await a proposal for a new
openClientWindow method on the PaymentRequestEvent.
We will need to update these examples.
self.addEventListener('paymentrequest', function(e) { e.respondWith(new Promise(function(resolve, reject) { self.addEventListener('message', listener = function(e) { self.removeEventListener('message', listener); if (e.data.hasOwnProperty('name')) { reject(e.data); } else { resolve(e.data); } }); // Note -- this will probably chage to e.openClientWindow(...) clients.openWindow("https://www.example.com/bobpay/pay") .then(function(windowClient) { windowClient.postMessage(e.data); }) .catch(function(err) { reject(err); }); })); });
Using the simple scheme described above, a trivial HTML page that is loaded into the payment handler window to implement the basic card scheme might look like the following:
<html> <body> <form id="form"> <table> <tr><th>Cardholder Name:</th><td><input name="cardholderName"></td></tr> <tr><th>Card Number:</th><td><input name="cardNumber"></td></tr> <tr><th>Expiration Month:</th><td><input name="expiryMonth"></td></tr> <tr><th>Expiration Year:</th><td><input name="expiryYear"></td></tr> <tr><th>Security Code:</th><td><input name="cardSecurityCode"></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() { /* See https://w3c.github.io/webpayments-methods-card/#basiccardresponse */ var basicCardResponse = {}; [ "cardholderName", "cardNumber","expiryMonth","expiryYear","cardSecurityCode"] .forEach(function(field) { basicCardResponse[field] = form.elements[field].value; }); /* See https://w3c.github.io/webpayments-payment-apps-api/#sec-app-response */ var paymentAppResponse = { methodName: "basic-card", details: details }; e.source.postMessage(paymentAppResponse); window.close(); } }); </script> </body> </html>
The Web Payments Working Group is also discussing Payment App authenticity; see the (draft) Payment Method Manifest.