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 present information about payment apps 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.

Introduction

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. The scope of this specification is Web-based payment apps, which are implemented as service workers.

This specification defines one class of products:

Conforming user agent

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]]

Dependencies

This specification relies on several other underlying specifications.

Payment Request API
The terms PaymentRequest, PaymentResponse, and user accepts the payment request algorithm are defined by the Payment Request API specification [[!PAYMENT-REQUEST-API]].
HTML5
The terms global object,origin, queue a task, browsing context, top-level browsing context, structured clone, event handler, event handler event type, trusted event, and current settings object are defined by [[!HTML5]].
ECMA-262 6th Edition, The ECMAScript 2015 Language Specification
The term Promise is defined by [[!ECMA-262-2015]].
DOM4

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.
WebIDL

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.
Secure Contexts
The terms secure context is defined by the Secure Contexts specification [[!POWERFUL-FEATURES]].
URL
The URL concept and URL parser are defined in [[!WHATWG-URL]].
Fetch
The terms Fetch, Request, Request Body, Request Method, Header List, Response, Context and Network Error are defined in [[!FETCH]].
Service Workers
The terms service worker service worker registration, active worker, installing worker, waiting worker, handle functional event, extend lifetime promises, and scope URL are defined in [[SERVICE-WORKERS]].

Payment App Model and Design Considerations

This section describes the capabilities and limitations of this specification in functional terms.

General Considerations

Decoupling and Trust

Identification

Registration and Unregistration

Payment App Matching and Selection

Payment App Invocation and Response

Definitions

Payment App Implementation Technology

user agent-based payment app
a payment app that runs in a user agent. User agent-based payment apps may elect to display a user interface. This decision is made at runtime, and may vary based on criteria of the app's choosing (such as how long it has been since the user last authenticated themselves).
native payment app
a payment app built with the operating system default technology stack that uses non-Web technologies.
ignored payment app
An app that the user has configured to not be displayed, or that the user agent ignores for security reasons.
payment app identifier
A unique identifier for a payment app (e.g., from a payment method manifest file). This specification defines these identifiers as to be service worker scope URLs. As such, they are not expected to be dereferenced.
payment app window
A service worker 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.

Payment App Registration States

registered payment app
a payment app that is "known" to the user agent for the purposes of the interactions described in this document.
This specification defines a payment app registration mechanism. Other registration mechanisms might co-exist with this one (e.g., on some platforms there may be a way to register a payment app directly with the operating system).
unregistered payment app
a payment app that is not known to the user agent, either because it has never been registered, or because it has been unregistered.
enabled payment app
A registered payment app with at least one enabled payment method.

Payment App Selection States

matching payment app
An enabled payment app that:
recommended payment app
a payment app suggested by the payee or user agent that may be used to handle a specific payment request.
displayed payment app
A matching payment app or recommended payment app with at least one matching payment method (i.e., presented by the user agent for user selection).
selected payment app
the payment app selected by the user to make a payment, but not yet invoked.
invoked payment app
the selected payment app that the user agent has invoked (executed) and passed payment app input data.

Payment Method Support

supported payment method
a payment method that a payment app has been designed to support. This payment method may or may not currently be enabled. A payment app MAY support more than one payment method.
unsupported payment method
a payment method that cannot be enabled by a payment app. Updates to a payment app may cause an unsupported payment method to become supported, or vice-versa.
enabled payment method
a supported payment method that a registered payment app is able to handle. The payment app must have at least one enabled payment method.

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.

Payment App Invocation and Response Data

payment app request
a request provided to the invoked payment app by the user agent to initiate a payment request. This request is a subset of the Payment Request.

The data passed between the user agent and the payment app will be serialized as JSON data.

payment app response
a response returned by an invoked payment app to the user agent, typically after payment authorization or other action taken through the payment app. This response, which will vary according to payment method, is then returned to the payee via the Payment Request API as part of the payment response.

Overview of Service-Worker-Based Approach

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. See issue 24 for information about launching the payment app in a secure context.

Here is the flow envisioned by this document:

  1. A service worker is registered and associates payment methods (and associated metadata) with the payment app.
  2. When the 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).
  3. When the user selects a user agent-based payment app, the corresponding service worker is activated, and it receives a PaymentRequestEvent.
  4. Once active, the payment app performs whatever steps are necessary to authenticate the user, process the payment, and return payment information to the payee. If interaction with the user is necessary, the payment app can open a payment app window for that purpose.
  5. Finally, once the payment app is finished with its processing, it resolves a Promise passed to it in the event. This causes the Promise<PaymentResponse> returned from PaymentRequest.show() to resolve.

Payment App Registration

Extensions to the ServiceWorkerRegistration interface

The Service Worker specification defines a ServiceWorkerRegistration interface [[!SERVICE-WORKERS]], which this specification extends.

        partial interface ServiceWorkerRegistration {
          readonly attribute PaymentAppManager paymentAppManager;
        };
      

PaymentAppManager interface

      interface PaymentAppManager {
        Promise<void> setManifest(PaymentAppManifest manifest);
        Promise<PaymentAppManifest> getManifest ();
      };
      

PaymentAppManager.setManifest()

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:

  1. Let promise be a new Promise.
  2. Return promise and asynchronously perform the remaining steps.
  3. If the current settings object is not a secure context, reject promise with a DOMException whose name is "SecurityError" and terminate these steps.
  4. Extension point: if the user agent has custom steps to invoke when registering payment apps, execute these steps (possibly rejecting promise with a DOMException whose name is "OperationError") and terminate these steps.
    Through this extension point we seek to avoid issues related to monkey patching.
  5. Let manifest be the value of the manifest argument.
  6. If manifest.canHandle is present but is not set to an executable function, reject promise with a DOMException whose name is "OperationError" and terminate these steps.
  7. Let registration be the PaymentAppManager's associated service worker registration.
  8. If registration has no active worker, run the following substeps:
    1. If registration has no installing worker and no waiting worker, reject promise with a DOMException whose name is "InvalidStateError" and terminate these steps.
    2. Wait for the installing worker or waiting worker of registration to become its active worker.
    3. If registration fails to activate either worker, reject promise with a DOMException whose name is "InvalidStateError" and terminate these steps.
    4. Once registration has an active worker, proceed with the steps below.
  9. Let paymentMethods be a list of identifiers from manifest section on supported payment methods. Ensure that the payment app is licensed to claim support for the payment methods (e.g., they are explicitly authorized, or the payment method imposes no constraints). Otherwise, reject with a DOMException whose name is "NotAllowedError" and terminate these steps.
  10. Ask the user whether they allow the payment app to be registered to handle the indicated payment methods (unless a prearranged trust relationship applies or the user has already granted or denied permission explicitly for this payment app).
  11. If permission is not granted, reject promise with a DOMException whose name is "NotAllowedError" and terminate these steps.
  12. Register the payment app with the user agent for future use, associating manifest's name and icons set with the payment app for user reference.
  13. For each PaymentAppOption present in the options field of the manifest:
    1. Add a new payment option to the payment app's registration, associating it with the PaymentAppOption name and icons fields.
    2. For each payment method indicated in the PaymentAppOption's enabledMethods field, associate the payment option and the payment app with the payment method.
  14. Resolve promise with undefined.

PaymentAppManager.getManifest()

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:

  1. Let promise be a new Promise.
  2. Return promise and asynchronously perform the remaining steps.
  3. If the current settings object is not a secure context, reject promise with a DOMException whose name is "SecurityError" and terminate these steps.
  4. If there is no PaymentAppManifest associated with the Service Worker, reject promise with a DOMException whose name is "AbortError" and terminate these steps.
  5. Retrieve the PaymentAppManifest associated with the Service Worker.
  6. Let manifest be the retrieved PaymentAppManifest.
  7. Resolve promise with manifest.

PaymentAppManifest interface

Issue 69: The Payment Apps Task Force has a goal of alignment with the draft Web App Manifest specification for data used to display payment app and options for selection by the user. More work is necessary to determine whether the Payment App API should reference (part of) the Web App Manifest specification, import definitions while that specification remains a draft, or define new terms.

      dictionary AvailableOption {
        required DOMString id;
        short modifier;
      };

      callback CanHandleCallback = Sequence<AvailableOption>
      (sequence<PaymentMethodData> methodData, PaymentDetails details);

      dictionary PaymentAppManifest {
        required DOMString name;
        sequence<ImageObject> icons;
        required sequence<PaymentAppOption> options;
        CanHandleCallback canHandle;
      };
      
name member
The name member is a string that represents the label for this payment app as it is usually displayed to the user.
icons member
The icons member is an array of image objects that can serve as iconic representations of the payment app when presented to the user for selection.
options member
The options 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.

canHandle member
The canHandle member indicates a function that is responsible for determining whether the payment app being registered is capable of handling a given request. If this member is not included in the manifest, the payment app is expected to be capable of handling all requests for the indicated payment method identifiers. canHandle runs with a globally unique origin, and is not allowed network access. The return value from canHandle is a sequence of zero or more HandledOptions dictionaries. Each dictionary represents a PaymentAppOption that can handle the request. The mandatory AvailableOption.id member corresponds to the PaymentAppOption.id for the PaymentOption corresponding to the dictionary. If present, the AvailableOption.modifier member is an index into the PaymentRequest.modifiers sequence, indicating which modifer applies to the corresponding PaymentAppOption.

PaymentAppOption dictionary

      dictionary PaymentAppOption {
        required DOMString name;
        sequence<ImageObjects> icons;
        required DOMString id;
        sequence<DOMString> enabledMethods;
      };
      
name member
The name member is a string that represents the label for this payment app as it is usually displayed to the user.
icons member
The icons member is an array of image objects that can serve as iconic representations of the payment app when presented to the user for selection.
id member
The id member is an identifier, unique within the PaymentAppManifest, that will be passed to the payment app to indicate the PaymentAppOption selected by the user.
enabledMethods member
The enabledMethods member lists the payment method identifiers of the payment methods enabled by this option. See also the canHandle function, which enables payment app developers to specify in finer granularity the conditions under which the payment app supports a payment method.

Registration Example

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({
             name: "ExampleApp",
             icons: [
                {
                  src: "icon/lowres.webp",
                  sizes: "48x48",
                  type: "image/webp"
                },{
                  src: "icon/lowres",
                  sizes: "48x48"
                } ]
             options: [
               {
                 name: "Visa ending ****4756",
                 id: "dc2de27a-ca5e-4fbd-883e-b6ded6c69d4f",
                 enabledMethods: ["basic-card"]
               },
               {
                 name: "My Bob Pay Account: john@example.com",
                 id: "c8126178-3bba-4d09-8f00-0771bcfd3b11",
                 enabledMethods: ["https://bobpay.com/"]
               },
               {
                 name: "Add new credit/debit card to ExampleApp",
                 id: "new-card",
                 enabledMethods: ["basic-card"]
               }
             ],
             canHandle: function (methodData, details) {
               // TODO: Code here to return mapping from payment option IDs
               // to methodData option indices.
             }
           });
         }).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.

Payment App Matching

When the mediator calculates acceptedMethods during the process of running the steps for the PaymentRequest.show() method, the means of determining whether a registered payment app should be made available for user selection is computed by performing the following steps:

  1. Set registeredMethods to an empty set.
  2. For each PaymentAppOption option in the payment app's PaymentAppManifest, add all entries in option.enabledMethods to registeredMethods.
  3. Set requestMethods to a set containing the values in the supportedMethods value of the PaymentMethodData from the PaymentRequest on which show() has been called.
  4. For each paymentDetailsModifier in details.modifiers in the PaymentDetails from the PaymentRequest on which show() has been called, add all entries in paymentDteailsModifier.supportedMethods to requestMethods.
  5. Set commonMethods to the set union of paymentMethods and requestMethods.
  6. If commonMethods is a null set, this payment app is not considered to be a candidate to handle the PaymentRequest and terminate these steps.
  7. If the PaymentAppManifest does not contain a canHandle attribute, then this payment app is a candidate for handling the PaymentRequest. Only those PaymentAppOptions that have enabledMethods containing at least one member of commonMethods are to be displayed to the user. Terminate these steps.
  8. Set methodDataCopy to a deep copy of the methodData parameter of the PaymentRequest.
  9. Set detailsCopy to a deep copy of the details parameter of the PaymentRequest.
  10. Create an isolated interpreted context, a JavaScript realm that operates with a globally unique origin. Access to the network must be disallowed from within this context.
  11. Populate the realm with a single global function, canHandle, set to the value of PaymentAppManifest.canHandle.
  12. Execute canHandle in the realm, passing methodDataCopy and detailsCopy as the parameters to the function. Set returnValue to the return value of the function.
  13. If the execution of canHandle results in an exception or returnValue is not a Sequence of AvailableOptions, then this payment app is not considered to be a candidate to handle the PaymentRequest. Terminate these steps.
  14. For each AvailableOption of returnValue, verify that the value of its modifier entry, if present, is an integer, no smaller than 0, and no larger than the largest index of PaymentRequest.modifiers sequence. Any AvailableOption that fails this validation is removed from the sequence.
  15. If returnValue has no remaining AvailableOptions, then this payment app is not considered to be a candidate to handle the PaymentRequest. Terminate these steps.
  16. If processing has reached this point, then the payment app is a candidate for processing the PaymentRequest. Each AvailableOption in returnValue corresponds to a matching PaymentAppOption that can service the request. If present the PaymentAppOption.modifier indicates which PaymentDetailsModifier will be applied by the corresponding PaymentAppOption.

Payment App Selection

Selectable App Information Display

The output of the payment method matching algorithm will be a list of matching payment apps and options from registered payment apps, and a list of recommended payment apps. The user agent presents this list of displayed payment apps to the user for selection. The user agent MUST enable the user to select any displayed payment app.

Matching Payment Apps

Recommended Payment Apps

What is the means by which the payee provides information about recommended payment apps in the call to payment request API? See issue 79.

User Experience Considerations

We have identified a number of user experiences that we would like to harmonize. Just a few examples here:

  1. User has no registered payment apps.
  2. User has apps with supported but no enabled payment methods.
  3. User has apps with supported and enabled payment methods.
  4. Merchant wishes to recommend a payment app to the user.
  5. User agent wishes to recommend a payment app that supports a payment method for which the user does not currently have a supporting payment app.

The following are examples of user agent ordering of selectable payment apps.

Payment App Invocation, Display and Response

Once the user has selected a payment app, the user agent is responsible for preparing a payment app request, invoking the payment app, providing the request data to the payment app, and returning the payment app response through the Payment Request API.

Payment App Request

The payment app request is conveyed using the following dictionary:
      dictionary PaymentAppRequest {
        DOMString origin;
        sequence<PaymentMethodData> methodData;
        PaymentItem total;
        sequence<PaymentDetailsModifier> modifiers;
        DOMString optionId;
      };
    
origin attribute
This attribute a string that indicates the origin of the payee web page. It MUST be formatted according to the "Unicode Serialization of an Origin" algorithm defined in section 6.1 of [[!RFC6454]].
methodData attribute
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 Method Data Population Algorithm defined below.
total attribute
This attribute indicates the total amount being requested for payment. It is initialized with a structured clone of the total field of the PaymentDetails provided when the corresponding PaymentRequest object was instantiated.
displayItems
The sequence of line items optionally provided by the payee. It is initialized with a structured clone of the displayItems field of the PaymentDetails provided when the corresponding PaymentRequest object was instantiated.
modifiers attribute
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.
optionId attribute
This attribute indicates the PaymentAppOption selected by the user. It corresponds to the id field provided during payment app registration.

Method Data Population Algorithm

To initialize the value of the methodData, the user agent MUST perform the following steps or their equivalent:

  1. Set registeredMethods to an empty set.
  2. For each PaymentAppOption option in the payment app's PaymentAppManifest, add all entries in option.enabledMethods to registeredMethods.
  3. Create a new empty Sequence.
  4. Set dataList to the newly created Sequence.
  5. For each item in PaymentRequest@[[\methodData]] in the corresponding payment request, perform the following steps:
    1. Set inData to the item under consideration.
    2. Set commonMethods to the set intersection of inData.supportedMethods and registeredMethods.
    3. If commonMethods is empty, skip the remaining substeps and move on to the next item (if any).
    4. Create a new PaymentMethodData object.
    5. Set outData to the newly created PaymentMethodData.
    6. Set outData.supportedMethods to a list containing the members of commonMethods.
    7. Set outData.data to a structured clone of inData.data.
    8. Append outData to dataList.
  6. Set methodData to dataList.

Modifiers Population Algorithm

To initialize the value of the modifiers, the user agent MUST perform the following steps or their equivalent:

  1. Set registeredMethods to an empty set.
  2. For each PaymentAppOption option in the payment app's PaymentAppManifest, add all entries in option.enabledMethods to registeredMethods.
  3. Create a new empty Sequence.
  4. Set modifierList to the newly created Sequence.
  5. For each item in PaymentRequest@[[\paymentDetails]].modifiers in the corresponding payment request, perform the following steps:
    1. Set inModifier to the item under consideration.
    2. Set commonMethods to the set intersection of inModifier.supportedMethods and registeredMethods.
    3. If commonMethods is empty, skip the remaining substeps and move on to the next item (if any).
    4. Create a new PaymentDetailsModifier object.
    5. Set outModifier to the newly created PaymentDetailsModifier.
    6. Set outModifier.supportedMethods to a list containing the members of commonMethods.
    7. Set outModifier.total to a structured clone of inModifier.total.
    8. Set outModifier.additionalDisplayItems to a structured clone of inModifier.additionalDisplayItems.
    9. Append outModifier to modifierList.
  6. Set modifiers to modifierList.

Payment App Response

The payment app response is conveyed using the following dictionary:
      dictionary PaymentAppResponse {
        DOMString methodName;
        object details;
      };
    
methodName attribute
The payment method identifier for the payment method that the user selected to fulfil the transaction.
details attribute
A JSON-serializable object that provides a payment method specific message used by the merchant to process the transaction and determine successful fund transfer.

Payment App Invocation

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 PaymentAppRequest 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.

Extension to ServiceWorkerGlobalScope

The Service Worker specification defines a ServiceWorkerGlobalScope interface [[!SERVICE-WORKERS]], which this specification extends.

        partial interface ServiceWorkerGlobalScope {
          attribute EventHandler onpaymentrequest;
        };
      
onpaymentrequest attribute
The onpaymentrequest attribute is an event handler whose corresponding event handler event type is paymentrequest.

The PaymentRequestEvent interface represents a received payment request.

The paymentrequest Event

The PaymentRequestEvent represents a received payment request.

      [Exposed=ServiceWorker]
      interface PaymentRequestEvent : ExtendableEvent {
        readonly attribute PaymentAppRequest appRequest;
        void respondWith(Promise<PaymentAppResponse>appResponse);
      };
      
appRequest attribute
This attribute contains the payment app request associated with this payment request.
respondWith method
This method is used by the payment app to provide a PaymentAppResponse when the payment successfully completes.

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:

  1. Let registration be the service worker registration corresponding to the user agent-based payment app selected by the user.
  2. If registration is not found and the selected app is a recommended app, register the service worker as described in , skipping user consent and user agent registration for future use in (steps 9-11).
  3. If registration is not found, reject the Promise that was created by PaymentRequest.show() with a DOMException whose value "InvalidStateError" and terminate these steps.
  4. Invoke the Handle Functional Event algorithm with a service worker registration of registration and callbackSteps set to the following steps:
    1. Set global to the global object that was provided as an argument.
    2. Create a trusted event, e, that uses the PaymentRequestEvent interface, with the event type paymentrequest, which does not bubble, cannot be canceled, and has no default action.
    3. Set the appRequest attribute of e to a new PaymentAppRequest instance, populated as described in .
    4. Dispatch e to global.
    5. Wait for all of the promises in the extend lifetime promises of e to resolve.
    6. If the payment app has not provided a payment app response as described in , reject the Promise that was created by PaymentRequest.show() with a DOMException whose value "OperationError".

Payment App Display

An invoked payment app may or may not need to display information about itself or request user input. Some examples of potential payment app displays include:

To support scenarios that involve visual display and user interaction, user agents MUST allow payment apps to call the clients.openWindow() method defined in [[!SERVICE-WORKERS]]. Because user agents can determine from event type that the window is part of a payment flow, the user agent SHOULD render the window in a way that is consistent with the flow and not confusing to the user. For example, we recommend that the payment app not be opened in a new browser tab, as this is too dissociated from the checkout context.

[[!SERVICE-WORKERS]] expects a user interaction to have occurred in order to open a Window. This user interaction is obvious when the user explicitly selects a payment app to make a payment. However, when user agent or payment app configuration allows payment app invocation without explicit user selection, the user agent MUST consider the paymentrequest event as the relevant user interaction for a clients.openWindow() request.

User agents SHOULD display the origin of a running payment app to the user.

Issue 73 asks whether it is possible to close a window open via clients.openWindow() programmatically.

The remainder of this section is currently 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.

Payment App Response

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 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:

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:

  1. Set appResponse to the PaymentAppResponse instance used to resolve the PaymentRequestEvent.respondWith Promise.
  2. If appResponse.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.
  3. Create a structured clone of appResponse.methodName and assign it to response.methodName.
  4. If appResponse.details is not present, run the payment app failure algorithm and terminate these steps.
  5. Create a structured clone of appResponse.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#visa",
          details: {
            card_number :  "1232343451234",
            expiry_month : "12",
            expiry_year :  "2020",
            cvv :          "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.

Example of handling the paymentrequest event

This 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.

      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);
            }
          });

          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 app 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>
    

Security and Privacy Considerations

Information about the User Environment

User Consent before Payment

Secure Communications

Payment App Authenticity

Data Validation

Private Browsing Mode