diff --git a/index.html b/index.html index 7cd5b9b..73ef54b 100644 --- a/index.html +++ b/index.html @@ -173,12 +173,13 @@
Event
type and the terms fire
+ 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. | +
Updates needed for payment method option definition.
- +WindowClient
used by browser-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 the may be invoked by the user agent, whatever technologies have been used to implement the payment app.
+We need a clearer introduction to the concept of a payment option, and how it relates to payment apps.
To support registration and unregistration, the user agent provides primitives that may be called from within a Web - page.
+In this specification we use service workers + to connect browsers with browser-based payment apps. We do so for + several reasons:
-Payment apps are registered with the user agent through a call to the register()
method of the API.
- The input to the registration process consists of:
payment_app_id
, a URL that identifies the app.request_url
which identifies the
- endpoint where the user agent submits payment requests.PaymentOption
- dictionaries to provide information displayed to the user to facilitate payment app selection.- See issue 20 about whether we should have two identifiers (or one) and expectations for dereferencing. -
++ The use of service workers restricts browser-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 33 and discussion about how to organize registration data. Can or should it be part of a "payment method" manifest? What is relationship to appmanifest for specifying icons, etc.? -
- +Here is the flow envisioned by this document:
+payment request API
is called, the browser
+ displays a list of registered service workers associated
+ with matching payment methods (along with any other payment apps
+ that may be available to the browser).Promise<PaymentResponse>
returned from
+ PaymentRequest.show()
to resolve.-dictionary PaymentOption { - DOMString label; - DOMString icon; - sequence<DOMString> enabled_methods; -}; -+
+ 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 setManifest
method, when invoked, MUST run the
+ following steps or their equivalent:
+
DOMException
whose name is
+ "SecurityError
" 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.
+ label
and
+ icon
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
+ retreive 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 teminate these steps.
+ + dictionary PaymentAppManifest { + DOMString label; + DOMString? icon; + sequence<PaymentAppOption> options; + }; +
label
memberlabel
member is a
- string that represents the lable for this option as it is usually displayed to the user when
- selecting a payment app.
+ The label
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.
+ enabled_methods
memberoptions
memberenabled_methods
member
- lists the payment method identifiers of the payment methods enabled by this option.
+ The options
+ member lists the payment method identifiers of the
+ payment methods enabled by this option.
This section will need to be updated in light of issue 33 and the draft proposal from Adam Roach.
- -
- The register
method is used to register, update, or unregister a payment app with a user agent.
-
- Payment apps are uniquely identifed by their payment_app_id
. To update the payment options
- offered by a registered payment app, a website calls register()
using the same
- payment_app_id
as was used when registering the app and pass in the new sequence of options. To
- unregister a payment app the website calls register()
with an empty sequence of options.
-
-partial interface PaymentApp { - static Promise<void> register (URLString payment_app_id, URLString request_url, sequence<PaymentOption> payment_options); -}; -+
+ dictionary PaymentAppOption { + DOMString label; + DOMString? icon; + DOMString id; + sequence<DOMString> enabledMethods; + }; +
payment_app_id
parameterrequest_url
- the registered payment options will be replaced with the new sequence of options provided.request_url
parameterlabel
member
- The request_url
identifies a service that accepts
- payment request messages via HTTP POST.
-
- The request_url
is the API entry point for the payment
- app and MUST have the same origin as the web application that attempts
- to register the payment app.
-
Native apps could be registered from Web pages, thus allowing the association of an origin to a native app.
+ Thelabel
member is a string that represents the
+ label for this option as it is usually displayed to the user
+ when selecting a payment app.
payment_options
parametericon
memberid
member
- The payment_options
is a sequence of one or more
- options that can be presented to the user for selection when the [[!PAYMENT-REQUEST-API]] is invoked.
-
id
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.
The register
method MUST act as follows:
SecurityError
.undefined
.The following example shows how to register a payment app:
-- PaymentApp.register( - "https://example.com/paymentapp/v1", - [ - { - label: "Visa ending ****4756", - icon: "...", - enabled_methods: ["https://webpayments.org/payment-methods/card/visa"] - }, - { - label: "My Bob Pay Account", - icon: "...", - enabled_methods: ["https://bobpay.com/"] - }, - { - label: "Add new credit/debit card to ExampleApp", - icon: "...", - enabled_methods: [ - "https://webpayments.org/payment-methods/card/visa", - "https://webpayments.org/payment-methods/card/mastercard", - "https://webpayments.org/payment-methods/card/amex" ] - } - ] - ).then(function(value) { - console.log("Installed https://example.com/paymentapp/v1 payment app"); // Success! - }, function(reason) { - console.log(reason); // Error! - }); -
The following example shows how to register a browser-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. +
+- What, if anything, should we say about registering native payment apps? + What, if anything, should we say about registering native payment + apps? +
++ Native payment apps could be registered from Web pages (is this + true?), thus allowing the association of an origin to a native + payment app.
+- We anticipate that the Payment Method Identifier specification will define the PMI matching algorithm. This + 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:
@@ -701,7 +980,7 @@+
What information is needed by the user agent to display selectable apps/options? This needs to be captured during registration.
@@ -754,7 +1033,8 @@- 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. + 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.
-This specification will describe a service worker approach to launching apps; see issue 33 and the draft proposal from Adam Roach. - -
The Working Group should discuss how to capture good practice for payment app development, with examples that illustrate common payment flows, different platforms, etc.. A public resource (e.g., on github) could foster contributions of good practice information from the developer community.
-- Let P be the intersection of payment methods supported by the payee and enabled by the selected payment option. - Send the data corresponding to P, as well as any global transaction data (total, etc.), and payee origin information (per issue 27) to the payment app. - The details depend on discussions about the shape of the Payment Request API. -
-- Once we have finalized the shape of the data that is returned from the matching process we must define a - deterministic algorithm for serializing this as JSON for transmission over the wire. -
-- This section will need to be updated in light of issue 33 and the draft proposal from Adam Roach.
- -The following algorithm is to be replace.
- + The payment app request data is conveyed using the following + dictionary: ++ dictionary PaymentAppRequestData { + DOMString origin; + sequence<PaymentMethodData> methodData; + PaymentItem total; + sequence<PaymentDetailsModifier> modifiers; + }; ++
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.
+
+ To initialize the value of the methodData
, the user agent
+ MUST perform the following steps or their equivalent:
+
request_url
member of the selected payment app.Member | -Value | -||
---|---|---|---|
URL |
- The value of app_url | -||
method |
- The value "POST " |
- ||
header list |
-
-
|
- ||
body |
- The value of payment request | -
enabledMethods
to
+ registeredMethods.
+ Sequence
.
text/html
go to the section below on Payment App Displayapplication/json
then go to the section below on Payment App ResponseSequence
.
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.
+ - We should get input form Web Platform and WebAppSec on how to best construct this request. -
-- A remote payment app may be getting payment authorization from the user via different channel (such as a - mobile app) so we need the app to be able to send keep-alive messages back to the browser while this is in - progress. -
-- If the user cancels the handling of the payment request then the app should return a response - with an appropriate error/response code. Will this be done through the HTTP status code or defined as a value - that is set in the response. +
+ To initialize the value of the modifiers
, the user agent
+ MUST perform the following steps or their equivalent:
Communication mail fail at various points in the flow; see design considerations for some ideas for managing this.
-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.
+ - The Working Group is still discussing how payment apps are displayed for user interaction. -
+- Payment apps are invoked via an HTTP request. If the resulting HTTP response body is text/html then the user agent - MUST render this content for the user. -
-PaymentApp.respond()
method available to the page that is
- rendered.
- This specification does not otherwise prescribe how user agents render the payment app. This may be in a new tab/window, in
- a special modal dialogue specifically for this purpose, in an iframe embedded in the web page of the payee
- website or any other mechanism the user agent defines.
+ 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 browser-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. Ths process is formally described in the following
+ sections.
- A payment app that runs in the user agent responds to a payment request by returning a new
- PaymentAppResponse
via the PaymentApp.respond()
method.
-
-dictionary PaymentAppResponse { - DOMString method; - object details; -}; --
method
membermethod
member is the payment method
- identifer of the payment method used to handle the payment request.
- details
memberdetails
member contains the payment method
- specific data that is expected in a payment response.
- 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 payment app submits the PaymentAppResponse
by calling the respond()
method and
- passing this in as a parameter.
-
-partial interface PaymentApp { - static void respond (PaymentAppResponse response); -}; --
The respond
method MUST act as follows:
- This should be a light algorithm that simply hands off to the steps defined in Payment Request + The PaymentRequestEvent represents a received payment + request.
-The following example shows how to respond to a payment request:
-- PaymentApp.respond( - "https://webpayments.org/payment-methods/card/visa", - { - card_number : "1232343451234", - expiry_month : "12", - expiry_year : "2020", - cvv : "123" - }); ++ [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
+ browser-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,
+ is not cancelable, 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 perferences that override such behavior, user interaction is
+ required during payment requests, in the form of payment app selection. As
+ a consquence, 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 browser + 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 response from the payment app in one of two forms:
+ The user agent receives a successful response from the payment app + through resolution of the Promise provided to therespondWith
+ 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 equiivalent: +
PaymentAppResponse
sent as a response to the HTTP request that invoked the appPaymentAppResponse
dictionary instance submitted via the PaymentApp.respond()
methodPaymentResponse
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 MUST use this PaymentAppResponse
to resolve the promise that was created
- by PaymentRequest.show when the user agent executed the user accepts the payment request algorithm
- as defined in [[!PAYMENT-REQUEST-API]].
-
- 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? +
+ 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 using a scheme 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> ++ +