This specification defines capabilities that enable Web applications to handle requests for payment.
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.
This specification defines a number of new features to allow web applications to handle requests for payments on behalf of users:
This specification does not address how software built with operating-system specific mechanisms (i.e., "native apps") handle payment requests.
In this document we envision the following flow:
An origin may implement a payment app with more than one service worker and therefore multiple payment handlers may be registered per origin. The handler that is invoked is determined by the selection made by the user of a payment instrument. The service worker which stored the payment instrument with its PaymentManager is the one that will be invoked.
A payment handler is a Web application that can handle a request for payment on behalf of the user.
The logic of a payment handler is driven by the payment methods that it supports. Some payment methods, such as basic-card expect little to no processing by the payment handler which simply returns payment card details in the response. It is then the job of the payee website to process the payment using the returned data as input.
In contrast, some payment methods, such as a crypto-currency payments or bank originated credit transfers, require that the payment handler initiate processing of the payment. In such cases the payment handler will return a payment reference, endpoint URL or some other data that the payee website can use to determine the outcome of the payment (as opposed to processing the payment itself).
Handling a payment request may include numerous interactions: with the user through a new window or other APIs (such as [[!WebCryptoAPI]]) or with other services and origins through web requests or other means.
This specification does not address these activities that occur between the payment handler accepting the PaymentRequestEvent and the payment handler returning a response. All of these activities which may be required to configure the payment handler and handle the payment request, are left to the implementation of the payment handler, including:
Thus, an origin will rely on many other Web technologies defined elsewhere for lifecycle management, security, user authentication, user interaction, and so on.
This specification does not address how third-party mobile payment apps interact (through proprietary mechanisms) with user agents, or how user agents themselves provide simple payment app functionality.
ServiceWorkerRegistration
interface
This specification extends the ServiceWorkerRegistration interface with the addition of a paymentManager attribute.
partial interface ServiceWorkerRegistration { readonly attribute PaymentManager paymentManager; };
The paymentManager attribute exposes payment handler functionality in the service worker.
[SecureContext, Exposed=(Window,Worker)] interface PaymentManager { [SameObject] readonly attribute PaymentInstruments instruments; [Exposed=Window] static Promise<PermissionState> requestPermission(); attribute DOMString userHint; };
The PaymentManager is used by payment handlers to manage their associated instruments and supported payment methods.
This attribute allows manipulation of payment instruments associated with a service worker (and therefore its payment handler). To be a candidate payment handler, a handler must have at least one registered payment instrument to present to the user. That instrument needs to match the payment methods and required capabilities specified by the payment request.
The user agent is NOT REQUIRED to prompt the user to grant permission to the origin for each new supported payment method or new payment instrument.
When called, this method executes the following steps:
When displaying payment handler name and icon, the user agent may use this string to improve the user experience. For example, a user hint of "**** 1234" can remind the user that a particular card is available through this payment handler. When a agent displays all payment instruments available through a payment handler, it may cause confusion to display the additional hint.
[SecureContext, Exposed=(Window,Worker)] interface PaymentInstruments { Promise<boolean> delete(DOMString instrumentKey); Promise<any> get(DOMString instrumentKey); Promise<sequence<DOMString>> keys(); Promise<boolean> has(DOMString instrumentKey); Promise<void> set(DOMString instrumentKey, PaymentInstrument details); Promise<void> clear(); };
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, if any.
When called, this method executes the following steps:
When called, this method executes the following steps:
undefined
.
When called, this method executes the following steps:
When called, this method executes the following steps:
When called, this method executes the following steps:
When called, this method executes the following steps:
dictionary PaymentInstrument { required DOMString name; sequence<ImageObject> icons; DOMString method; object capabilities; };
dictionary ImageObject { required USVString src; DOMString sizes; DOMString type; };
When this algorithm with inputImages parameter is invoked, the user agent must run the following steps:
According to the step 2.3, it is also possible to use the relative url for image.src. The following examples illustrate how relative URL resolution works in different execution contexts.
<-- In this example, code is located in https://www.example.com/bobpay/index.html --> <script> const instrumentKey = "c8126178-3bba-4d09-8f00-0771bcfd3b11"; const registration = await navigator.serviceWorker.register("/register/sw.js"); await registration.paymentManager.paymentInstruments.set({ instrumentKey, { name: "My Bob Pay Account: john@example.com", method: "https://bobpay.com", icons: [{ src: "icon/lowres.webp", sizes: "48x48", type: "image/webp" }] }); const { storedInstrument } = await registration.paymentManager.paymentInstruments.get(instrumentKey); // storedInstrument.icons[0].src == "https://www.example.com/bobpay/icon/lowres.webp"; </script>
// In this example, code is located in https://www.example.com/register/sw.js const instrumentKey = "c8126178-3bba-4d09-8f00-0771bcfd3b11"; await self.registration.paymentManager.paymentInstruments.set({ instrumentKey, { name: "My Bob Pay Account: john@example.com", method: "https://bobpay.com", icons: [{ src: "../bobpay/icon/lowres.webp", sizes: "48x48", type: "image/webp" }] }); const { storedInstrument } = await registration.paymentManager.paymentInstruments.get(instrumentKey); // storedInstrument.icons[0].src == "https://www.example.com/bobpay/icon/lowres.webp";
The following example shows how to register a payment handler:
button.addEventListener("click", async() => { if (!window.PaymentManager) { return; // not supported, so bail out. } const result = await PaymentManager.requestPermission(); if (result !== "granted") { return; } const registration = await navigator.serviceWorker.register("/sw.js"); // Excellent, we got it! Let's now set up the user's cards. await addInstruments(registration); }, { once: true }); function addInstruments(registration) { return Promise.all([ registration.paymentManager.instruments.set( "dc2de27a-ca5e-4fbd-883e-b6ded6c69d4f", { name: "Visa ending ****4756", method: "basic-card", capabilities: { supportedNetworks: ["visa"], supportedTypes: ["credit"] } }), registration.paymentManager.instruments.set( "c8126178-3bba-4d09-8f00-0771bcfd3b11", { name: "My Bob Pay Account: john@example.com", method: "https://bobpay.com" }), registration.paymentManager.instruments.set( "new-card", { name: "Add new credit/debit card to ExampleApp", method: "basic-card", capabilities: { supportedNetworks: ["visa", "mastercard", "amex", "discover"], supportedTypes: ["credit", "debit", "prepaid"] } }), ]); };
If the payment handler supports CanMakePaymentEvent, the user agent may use it to help with filtering of the available payment handlers.
Implementations may impose a timeout for developers to respond to the
CanMakePaymentEvent. If the timeout expires, then the
implementation will behave as if respondWith() was called with
false
.
This specification extends the ServiceWorkerGlobalScope interface.
partial interface ServiceWorkerGlobalScope { attribute EventHandler oncanmakepayment; };
The oncanmakepayment attribute is an event handler whose corresponding event handler event type is canmakepayment.
The CanMakePaymentEvent is used to check whether the payment handler is able to respond to a payment request.
[Constructor(DOMString type, CanMakePaymentEventInit eventInitDict), Exposed=ServiceWorker] interface CanMakePaymentEvent : ExtendableEvent { readonly attribute USVString topLevelOrigin; readonly attribute USVString paymentRequestOrigin; readonly attribute FrozenArray<PaymentMethodData> methodData; readonly attribute FrozenArray<PaymentDetailsModifier> modifiers; void respondWith(Promise<boolean>canMakePaymentResponse); };
The topLevelOrigin, paymentRequestOrigin, methodData, and modifiers members share their definitions with those defined for PaymentRequestEvent.
This method is used by the payment handler to indicate whether it can respond to a payment request. The respondWith(canMakePaymentPromise) method MUST act as follows:
dictionary CanMakePaymentEventInit : ExtendableEventInit { USVString topLevelOrigin; USVString paymentRequestOrigin; sequence<PaymentMethodData> methodData; sequence<PaymentDetailsModifier> modifiers; };
The topLevelOrigin, paymentRequestOrigin, methodData, and modifiers members share their definitions with those defined for PaymentRequestEvent.
Upon receiving a PaymentRequest, the user agent MUST run the following steps:
This example shows how to write a service worker that listens to the CanMakePaymentEvent. When a CanMakePaymentEvent is received, the service worker always returns true.
self.addEventListener("canmakepayment", function(e) { e.respondWith(true); });
Given a PaymentMethodData and a PaymentInstrument that
match on payment method identifier, this algorithm returns
true
if this instrument can be used for payment:
"supported_origins": "*"
in its payment method
manifest, filter based on capabilities:
false
.
true
.
Example of how a payment handler should provide the list of all its active cards to the browser.
await navigator.serviceWorker.register("/pw/app.js"); const registration = await navigator.serviceWorker.ready; registration.paymentManager.userHint = "(Visa ****1111)"; await registration.paymentManager.instruments.set( "12345", { name: "Visa ****1111", icons: [{ src: "/pay/visa.png", sizes: "32x32", type: "image/png", }], method: "basic-card", capabilities: { supportedNetworks: ["visa"], supportedTypes: ["credit"], }, });
In this case, new PaymentRequest([{supportedMethods:
"basic-card"}], shoppingCart).canMakePayment()
should return
true
because there's an active card in the payment
handler. Note that new PaymentRequest([{supportedMethods:
"basic-card", data: {supportedTypes: ["debit"]}}],
shoppingCart).canMakePayment()
would return
false
because of mismatch in
supportedTypes
in this example.
Once the user has selected an Instrument, the user agent fires a PaymentRequestEvent and uses the subsequent PaymentHandlerResponse to create a PaymentReponse for [[!payment-request]].
Payment Request API supports delegation of responsibility to manage an abort to a payment app. There is a proposal to add a paymentRequestAborted event to the Payment Handler interface. The event will have a respondWith method that takes a boolean parameter indicating if the paymentRequest has been successfully aborted.
This specification extends the ServiceWorkerGlobalScope interface.
partial interface ServiceWorkerGlobalScope { attribute EventHandler onpaymentrequest; };
The onpaymentrequest attribute is an event handler whose corresponding event handler event type is PaymentRequestEvent.
The PaymentRequestEvent represents the data and methods available to a Payment Handler after selection by the user. The user agent communicates a subset of data available from the PaymentRequest to the Payment Handler.
[Constructor(DOMString type, PaymentRequestEventInit eventInitDict), Exposed=ServiceWorker] interface PaymentRequestEvent : ExtendableEvent { readonly attribute USVString topLevelOrigin; readonly attribute USVString paymentRequestOrigin; readonly attribute DOMString paymentRequestId; readonly attribute FrozenArray<PaymentMethodData> methodData; readonly attribute object total; readonly attribute FrozenArray<PaymentDetailsModifier> modifiers; readonly attribute DOMString instrumentKey; Promise<WindowClient?> openWindow(USVString url); void respondWith(Promise<PaymentHandlerResponse>handlerResponsePromise); };
Returns a string that indicates the origin of the top level payee web page. This attribute is initialized by Handling a PaymentRequestEvent.
Returns a string that indicates the origin where a PaymentRequest was initialized. When a PaymentRequest is initialized in the topLevelOrigin, the attributes have the same value, otherwise the attributes have different values. For example, when a PaymentRequest is initialized within an iframe from an origin other than topLevelOrigin, the value of this attribute is the origin of the iframe. This attribute is initialized by Handling a PaymentRequestEvent.
When getting, the paymentRequestId attribute returns the [[\details]].id from the PaymentRequest that corresponds to this PaymentRequestEvent.
This attribute contains PaymentMethodData 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 MethodData Population Algorithm defined below.
This attribute indicates the total amount being requested for payment. It is of type PaymentCurrencyAmount dictionary as defined in [[payment-request]], and initialized with a structured clone of the total field of the PaymentDetailsInit provided when the corresponding PaymentRequest object was instantiated.
This sequence of PaymentDetailsModifier 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.
This attribute indicates the PaymentInstrument selected by the user. It corresponds to the instrumentKey provided to the PaymentManager.instruments interface during registration. An empty string means that the user did not choose a specific PaymentInstrument.
This method is used by the payment handler to show a window to the user. When called, it runs the open window algorithm.
This method is used by the payment handler to provide a PaymentHandlerResponse when the payment successfully completes. When called, it runs the Respond to PaymentRequest Algorithm with event and handlerResponsePromise as arguments.
Should payment apps receive user data stored in the user agent upon explicit consent from the user? The payment app could request permission either at installation or when the payment app is first invoked.
dictionary PaymentRequestEventInit : ExtendableEventInit { USVString topLevelOrigin; USVString paymentRequestOrigin; DOMString paymentRequestId; sequence<PaymentMethodData> methodData; PaymentCurrencyAmount total; sequence<PaymentDetailsModifier> modifiers; DOMString instrumentKey; };
The topLevelOrigin, paymentRequestOrigin, paymentRequestId, methodData, total, modifiers, and instrumentKey members share their definitions with those defined for PaymentRequestEvent
To initialize the value of the methodData, the user agent MUST perform the following steps or their equivalent:
To initialize the value of the modifiers, the user agent MUST perform the following steps or their equivalent:
Instances of CanMakePaymentEvent are created with the internal slots in the following table:
Internal Slot | Default Value | Description (non-normative) |
---|---|---|
[[\resultPromise]] | null | This value is set to the pending promise to get back the result when event is triggered from the associated PaymentRequest. |
Instances of PaymentRequestEvent are created with the internal slots in the following table:
Internal Slot | Default Value | Description (non-normative) |
---|---|---|
[[\windowClient]] | null | The currently active WindowClient. This is set if a payment handler is currently showing a window to the user. Otherwise, it is null. |
[[\fetchedImage]] | undefined | This value is a result of fetching image object or a fallback image provided by the user agent. |
[[\respondWithCalled]] | false | YAHO |
Upon receiving a PaymentRequest by way of PaymentRequest.show() and subsequent user selection of a payment instrument, the user agent MUST run the following steps:
An invoked payment handler may or may not need to display information about itself or request user input. Some examples of potential payment handler display include:
A payment handler that requires visual display and user interaction, may call openWindow() to display a page to the user.
Since user agents know that this method is connected to the PaymentRequestEvent, they SHOULD render the window in a way that is consistent with the flow and not confusing to the user. The resulting window client is bound to the tab/window that initiated the PaymentRequest. A single payment handler SHOULD NOT be allowed to open more than one client window using this method.
This algorithm resembles the Open Window Algorithm in the Service Workers specification.
Should we refer to the Service Workers specification instead of copying their steps?
about:blank
, return a
Promise rejected with a TypeError.
This example shows how to write a service worker that listens to the PaymentRequestEvent. When a PaymentRequestEvent is received, the service worker opens a window to interact with the user.
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); } }); e.openWindow("https://www.example.com/bobpay/pay") .then(function(windowClient) { windowClient.postMessage(e.data); }) .catch(function(err) { reject(err); }); })); });
The Web Payments Working Group plans to revisit these two examples.
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:
<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/payment-handler/#paymenthandlerresponse-dictionary */ var paymentAppResponse = { methodName: "basic-card", details: details }; e.source.postMessage(paymentAppResponse); window.close(); } }); </script>
dictionary PaymentHandlerResponse { DOMString methodName; object details; };
The payment method identifier for the payment method that the user selected to fulfil the transaction.
A JSON-serializable object that provides a payment method specific message used by the merchant to process the transaction and determine successful fund transfer.
The user agent receives a successful response from the payment handler through resolution of the Promise provided to the respondWith() function of the corresponding PaymentRequestEvent interface. The application is expected to resolve the Promise with a PaymentHandlerResponse 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 are left to implementers. Acceptable behaviors include, but are not limited to:
When this algorithm is invoked with event and handlerResponsePromise parameters, the user agent MUST run the following steps:
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]] defines an ID that parties in the ecosystem (including payment app providers and payees) can use for reconciliation after network or other failures.
The Web Payments Working Group is also discussing Payment App authenticity; see the (draft) Payment Method Manifest.
true
.
When ordering payment handlers and payment instruments, the user agent is expected to honor user preferences over other preferences. User agents are expected to permit manual configuration options, such as setting a preferred payment handler or instrument display order for an origin, or for all origins.
User experience details are left to implementers.
This specification relies on several other underlying specifications.
TypeError
,
and JSON.stringify
are
defined by [[!ECMASCRIPT]].
When this specification says to throw an error, the user agent must throw an error as described in [[!WEBIDL]]. When this occurs in a sub-algorithm, this results in termination of execution of the sub-algorithm and all ancestor algorithms until one is reached that explicitly describes procedures for catching exceptions.
The algorithm for converting an ECMAScript value to a dictionary is defined by [[!WEBIDL]].
DOMException and the following DOMException types from [[!WEBIDL]] are used:
InvalidAccessError
"
InvalidStateError
"
NotAllowedError
"
NotFoundError
"
OperationError
"
SecurityError
"
ServiceWorkerRegistration
,
ServiceWorkerGlobalScope
,
handle
functional event, extend
lifetime promises,pending
promises count, containing
service worker registration, uninstalling flag,
Try Clear
Registration, Try Activate, and
scope URL are
defined in [[!SERVICE-WORKERS]].
There is only one class of product that can claim conformance to this specification: a user agent.
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.
User agents MAY impose implementation-specific limits on otherwise unconstrained inputs, e.g., to prevent denial of service attacks, to guard against running out of memory, or to work around platform-specific limitations. When an input exceeds implementation-specific limit, the user agent MUST throw, or, in the context of a promise, reject with, a TypeError optionally informing the developer of how a particular input exceeded an implementation-specific limit.