diff --git a/index.html b/index.html index 55c95b4..9a28d61 100644 --- a/index.html +++ b/index.html @@ -125,7 +125,7 @@
InvalidStateError
NotFoundError
SecurityError
partial interface ServiceWorkerRegistration { - readonly attribute PaymentAppManager paymentAppManager; + readonly attribute PaymentManager paymentManager; };-
- interface PaymentAppManager { - attribute PaymentAppOptions options; ++ -PaymentManager interface
++ interface PaymentManager { + attribute PaymentInstruments instruments; attribute PaymentWallets wallets; };-
- +
options
attributeinstruments
attribute- - This attribute allows manipulation of payment options + This attribute allows manipulation of payment instruments associated with a payment handler. To be presented to the user as part of the payment request flow, payment handlers must have at least one registered payment - option, and that option needs to match the payment + Instrument, and that Instrument needs to match the payment methods and required capabilities specified by the payment request. -
Is "options" the right term here? We - don't want this to get conflated with "PaymentOptions" in - the payment request specification. Other alternatives - might be "instruments" or "accounts."
wallets
attribute- This attribute allows payment handlers to group payment - options together and provide a name and icon for such a + instruments together and provide a name and icon for such a group (e.g., to group together "business account" payment instruments separately from "personal account" payment instruments). The use of this grouping mechanism by payment handlers is completely optional. If payment handlers use - this grouping mechanism, then matching payment options that + this grouping mechanism, then matching payment instruments that do not appear in any groups should still be presented to users by the browser for selection.
- PaymentAppOptions interface
-- interface PaymentAppOptions { - Promise<boolean> delete(DOMString optionKey); - Promise<PaymentAppOption> get(DOMString optionKey); ++ -PaymentInstruments interface
++ interface PaymentInstruments { + Promise<boolean> delete(DOMString instrumentKey); + Promise<PaymentInstrument> get(DOMString instrumentKey); Promise<sequence<DOMString>> keys(); - Promise<boolean> has(DOMString optionKey); - Promise<void> set(DOMString optionKey, - PaymentAppOption details); + Promise<boolean> has(DOMString instrumentKey); + Promise<void> set(DOMString instrumentKey, + PaymentInstrument details); };-Where it appears, The
optionKey
parameter is - a unique identifier for the option; it will be passed to the - payment handler to indicate the PaymentAppOption +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.
-TODO: flesh out details after the WG reaches - general consensus on the shape of this API.
++ +
+ +- +
delete
method- + When called, this method executes the following steps: +
+ ++
+- Let p be a new promise.
+- Run the following steps in parallel:
++
+- If the collection contains a PaymentInstrument + with a matching
+instrumentKey
, remove it from + the collection and resolve p with true.- Otherwise, resolve p with false.
+- Return p
+- +
get
method- + When called, this method executes the following steps: +
+ ++
+- Let p be a new promise.
+- Run the following steps in parallel:
++
+- If the collection contains a PaymentInstrument + with a matching
instrumentKey
, resolve p + with that PaymentInstrument. +- Otherwise, reject p with a + DOMException whose value is + "NotFoundError". +
- Return p
+- +
keys
method- + When called, this method executes the following steps: +
+ ++
+- Let p be a new promise.
+- Run the following steps in parallel:
++
+- Resolve p with a sequence that contains all + the
instrumentKey
s for the PaymentInstruments + contained in the collection. +- Return p
+- +
has
method- + When called, this method executes the following steps: +
+ ++
+- Let p be a new promise.
+- Run the following steps in parallel:
++
+- If the collection contains a PaymentInstrument + with a matching
+instrumentKey
, resolve p + with true.- Otherwise, resolve p with false.
+- Return p
+- +
set
method- + When called, this method executes the following steps: +
++
+- Let p be a new promise.
+- Run the following steps in parallel:
++
+- If the collection contains a PaymentInstrument + with a matching
instrumentKey
, replace it with + the PaymentInstrument indetails
. +- Otherwise, PaymentInstrument + insert the PaymentInstrument in
details
+ as a new member of the collection and associate it with the + keyinstrumentKey
. +- Resolve p.
+- Return p
++ return paymentManager.instruments.keys().then((keys) => { + Promise.all(keys.map(paymentManager.instruments.delete(key))) + }); ++ +- -PaymentAppOption dictionary
-- dictionary PaymentAppOption { ++ PaymentInstrument dictionary
++ dictionary PaymentInstrument { required DOMString name; sequence<ImageObjects> icons; sequence<DOMString> enabledMethods; @@ -397,26 +491,26 @@PaymentAppOption dictionary
name
member- The
name
member is a string that - represents the label for this payment option as it is + represents the label for this payment Instrument 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 option when presented to the user for + payment Instrument when presented to the user for selection.enabledMethods
member- The
enabledMethods
+ "payment-instrument-enabled-methods">enabledMethods
member lists the payment method identifiers of the - payment methods enabled by this option. + payment methods enabled by this Instrument.capabilities
member- The
capabilities
+ "payment-instrument-capabilities">capabilities
member contains a list of payment-method-specific capabilities that this payment handler is capable of - supporting for this option. For example, for the + supporting for this Instrument. For example, for thebasic-card
payment method, this object will consist of an object with two fields: one forsupportedNetworks
, and another for @@ -425,7 +519,7 @@PaymentAppOption dictionary
PaymentWallets interface
-+interface PaymentWallets { Promise<boolean> delete(DOMString walletKey); Promise<WalletDetails> get(DOMString walletKey); @@ -437,16 +531,103 @@PaymentWallets interface
Where it appears, The
-walletKey
parameter is a unique identifier for the wallet.TODO: flesh out details after the WG reaches - general consensus on the shape of this API.
++ +
+- +
delete
method- + When called, this method executes the following steps: +
+ ++
+- Let p be a new promise.
+- Run the following steps in parallel:
++
+- If the collection contains a WalletDetails + with a matching
+walletKey
, remove it from + the collection and resolve p with true.- Otherwise, resolve p with false.
+- Return p
+- +
get
method- + When called, this method executes the following steps: +
+ ++
+- Let p be a new promise.
+- Run the following steps in parallel:
++
+- If the collection contains a WalletDetails + with a matching
walletKey
, resolve p + with that WalletDetails. +- Otherwise, reject p with a + DOMException whose value is + "NotFoundError". +
- Return p
+- +
keys
method- + When called, this method executes the following steps: +
+ ++
+- Let p be a new promise.
+- Run the following steps in parallel:
++
+- Resolve p with a sequence that contains all + the
walletKey
s for the WalletDetailss + contained in the collection. +- Return p
+- +
has
method- + When called, this method executes the following steps: +
+ ++
+- Let p be a new promise.
+- Run the following steps in parallel:
++
+- If the collection contains a WalletDetails + with a matching
+walletKey
, resolve p + with true.- Otherwise, resolve p with false.
+- Return p
+- +
set
method- + When called, this method executes the following steps: +
+ ++
+- Let p be a new promise.
+- Run the following steps in parallel:
++
+- If the collection contains a WalletDetails + with a matching
walletKey
, replace it with + the WalletDetails indetails
. +- Otherwise, WalletDetails + insert the WalletDetails in
details
+ as a new member of the collection and associate it with the + keywalletKey
. +- Resolve p.
+- Return p
+- WalletDetails interface
+WalletDetails dictionary
dictionary WalletDetails { required DOMString name; sequence<ImageObject> icons; - required sequence<DOMString> optionKeys; + required sequence<DOMString> instrumentKeys; };@@ -458,68 +639,111 @@
WalletDetails interface
- The
-icons
member is an array of image objects that can serve as iconic representations of the wallet when presented to the user for selection.- -
optionKeys
member- TODO
+- +
instrumentKeys
member- The
instrumentKeys
member is a + list ofinstrumentKey
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. +Registration Example
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. +- navigator.serviceWorker.register('/exampleapp.js') - .then(function(registration) { - registration.paymentAppManager.options.set( - "dc2de27a-ca5e-4fbd-883e-b6ded6c69d4f", - { - name: "Visa ending ****4756", - enabledMethods: ["basic-card"], - capabilities: { - supportedNetworks: ['visa'], - supportedTypes: ['credit'] - } - }); + 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" }); - registration.paymentAppManager.options.set( - "c8126178-3bba-4d09-8f00-0771bcfd3b11", - { - name: "My Bob Pay Account: john@example.com", - enabledMethods: ["https://bobpay.com/"] - }); + 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 }); - registration.paymentAppManager.options.set( - "new-card", - { - name: "Add new credit/debit card to ExampleApp", - enabledMethods: ["basic-card"], - capabilities: { - supportedNetworks: - ['visa','mastercard','amex','discover'], - supportedTypes: ['credit','debit','prepaid'] - } - }); + 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.paymentAppManager.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" - } - ], - optionKeys: [ - "dc2de27a-ca5e-4fbd-883e-b6ded6c69d4f", - "c8126178-3bba-4d09-8f00-0771bcfd3b11", - "new-card" - ] + 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 @@ -528,8 +752,8 @@
Registration Example
upon.- Origin and Option Display for Selection
++ Origin and Instrument Display for Selection
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 @@ -566,9 +790,9 @@
Ordering of Payment Handlers
- Display of Options
+Display of Instruments
The user agent MUST enable - the user to select any displayed Option.
+ the user to select any displayed Instrument.-
- At a minimum, we expect user agents to display an icon and label for each matching origin to help the user make a @@ -582,17 +806,17 @@
Display of Options
card's brand and the last four digits of the card to remind the user which cards the origin knows about.Issue 98. There has been pushback to always requiring - display of options (e.g., on a mobile devices). User agents - can incrementally show options. Or user agents can return an - empty option ID and it becomes the payment app's - responsibility to display options to the user.
+ 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.- Grouping of Options
-At times, the same origin may wish to group options with +
Grouping of Instruments
+At times, the same origin may wish to group instruments with greater flexibility and granularity than merely "by origin." These use cases include:
@@ -602,48 +826,50 @@
Grouping of Options
business wallet vs personal wallet)
A Wallet is a grouping of Options +
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 Options from how payment handlers respond to + 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 Options. The payment handler would - receive information about the selected option and could take + 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 option).
-
+
Issue 98. Should we require that, if displayed,
- individual Options must be selectable? Or should we allow
- flexibility that Options may be displayed, but selecting any
+ 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 Options in a submenu.
Once the user has selected an Option, the user agent fires a +
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]].
- dictionary PaymentAppRequest { - DOMString origin; - sequence<PaymentMethodData> methodData; - PaymentItem total; - sequence<PaymentDetailsModifier> modifiers; - DOMString optionId; + 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; };
id
attributeid
attribute returns this the
+ [[\details]].id
from the PaymentRequest that
+ corresponds to this PaymentAppRequest
.
+ methodData
attributePaymentMethodData
@@ -694,17 +926,13 @@ optionId
attributeinstrumentId
attributeid
field provided during registration.
See - issue 91 for discussion about the inclusion of line items - in the Payment App Request.
To initialize the value of the methodData
,
@@ -713,10 +941,10 @@
PaymentAppManager.options
, add all entries
- in option.enabledMethods
to
+ PaymentManager.instruments
, add all entries
+ in instrument.enabledMethods
to
registeredMethods.
Sequence
.PaymentAppManager.options
, add all entries
- in option.enabledMethods
to
+ PaymentManager.instruments
, add all entries
+ in instrument.enabledMethods
to
registeredMethods.
Sequence
.dictionary PaymentAppResponse { @@ -1021,12 +1250,13 @@Payment App Response
The payment app response paymentRequestEvent.respondWith(new Promise(function(accept,reject) { /* ... processing may occur here ... */ accept({ - methodName: "basic-card#visa", + methodName: "basic-card", details: { - card_number : "1232343451234", - expiry_month : "12", - expiry_year : "2020", - cvv : "123" + cardHolderName: "John Smith", + cardNumber: "1232343451234", + expiryMonth: "12", + expiryYear : "2020", + cardSecurityCode: "123" } }); }); @@ -1049,9 +1279,9 @@Example of handling the
paymentrequest
Per issue 97, we expect not to use - clients.OpenWindow and instead await a proposal for a new - openClientWindow method. We will need to update these - examples.
+clients.OpenWindow()
and instead await a proposal for a new + openClientWindow method on the PaymentRequestEvent. + We will need to update these examples.@@ -1066,6 +1296,7 @@Example of handling the
paymentrequest
} }); + // Note -- this will probably chage to e.openClientWindow(...) clients.openWindow("https://www.example.com/bobpay/pay") .then(function(windowClient) { windowClient.postMessage(e.data); @@ -1182,5 +1413,6 @@Private Browsing Mode