Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to set default instrument for given handler #173

Closed
zkoch opened this issue Jun 6, 2017 · 30 comments
Closed

Ability to set default instrument for given handler #173

zkoch opened this issue Jun 6, 2017 · 30 comments

Comments

@zkoch
Copy link

zkoch commented Jun 6, 2017

I'm wondering if there's a compromise between "show all the instruments for a given handler" and "show none of the instruments for a given handler" (Chrome's current implementation).

What if we allow a Handler to set a "default" instrument. It would enable something like this:

[ ] BobPay (Visa **** 4111)
[ ] PalPay (bob@bob.com)

Where the parenthetical in the above is the label for the defaulted instrument (if one exists).

This would enable single-click buy flows where the user is still informed about what the default action is when they hit pay, with the option preserved by the Handler to "step up" the user if necessary to a more involved flow.

@ianbjacobs
Copy link
Contributor

Can you say more about what happens when you click on BobPay? If I understand correctly,
you have this sort of user experience in mind:

  1. I have previously configured a default option within BobPay
  2. When I select BobPay, the browser invokes BobPay.
  3. BobPay then does whatever it does. It might, for example, prompt me to enter the CVV
    for the default card, and include a link for "other options" if I prefer to open the full set of
    options.

Ian

@zkoch
Copy link
Author

zkoch commented Jun 6, 2017

Can you say more about what happens when you click on BobPay? If I understand correctly,
you have this sort of user experience in mind:

Presumably whatever BobPay wants. But the idea is that if they chose not to call openClientWindow and just charge directly, it would be clear what instrument is being charged.

@adrianhopebailie
Copy link
Contributor

@zkoch when the payment handler receives the request would it be able to determine if the display showed all instruments or just the default?

@zkoch
Copy link
Author

zkoch commented Jun 12, 2017

Good question. I don't think so, but we could consider it. My original idea was just to pass in that instrumentKey with the event, just as if the user had selected it from a set of options.

@adrianhopebailie
Copy link
Contributor

I think it's difficult for a payment handler to do the right thing if they don't know what options the user was presented.

@zkoch
Copy link
Author

zkoch commented Jun 12, 2017

Isn't the "right thing" to follow the instrumentKey that gets optionally passed in as part of the request?

@ianbjacobs
Copy link
Contributor

ianbjacobs commented Jun 12, 2017

It seems like this could be managed through the definition of the default instrument:

  • The PaymentInstrument dictionary includes an optional attribute: defaultInstrument (boolean value). At most one defaultInstrument may be specified within the scope of
    PaymentManager.PaymentInstruments or within the scope of a PaymentWallet.

  • A browser displays EITHER a defaultInstrument OR the other specified PaymentInstrument(s).
    The browser makes a choice based on context.

  • This means:

    • Payment handlers can treat the display of default information specially if they wish.
    • Payment handlers know (by instrumentKey) whether the default was shown
      or whether the full set was shown. This lets the payment app adjust user flow accordingly
      upon selection.

Note: There may be better ways to identify a default instrument (e.g., PaymentInstrumentDefault is a subclass of PaymentInstrument). I defer to the API specialists for good practice.

@adrianhopebailie
Copy link
Contributor

adrianhopebailie commented Jun 13, 2017

Payment handlers know (by instrumentKey) whether the default was shown
or whether the full set was shown. This lets the payment app adjust user flow accordingly
upon selection.

This is not true.

Example:

I install BobPay and have 2 instruments loaded (Blue and Green). I set BobPay (Blue) as my default. I want to make a payment and am presented with options.

Scenario 1: I am presented with BobPay (Blue) and BobPay (Green) because the browser decides to show both instruments.

Scenario 2: I am presented with only BobPay (Blue) because the browser only shows the default and I have selected `BobPay (Blue) as default.

BobPay want me to have a have a really efficient check-out for Scenario 1, so if I select BobPay (Blue) they don't even show me a window they just process the payment using that instrument.

But, BobPay also want me to be able to use BobPay (Green) and they can't be sure I am always given all the instruments as options. So what do they do?

In Scenario 2, if I wanted to pay with BobPay (Green) there is no way for me to get to that option. I can select BobPay (Blue) hoping that when the BobPay handler loads it will give me a chance to specifiy that I want to use the Green instrument but because of the efficiency built into BobPay for scenario 1 we know that won't happen.

When the BobPay handler gets the payment request event it just sees an instrumentKey for BobPay (Blue), it has no idea if I was shown other options. Handlers are forced to either be safe and prompt me to confirm that i want to use BobPay (Blue), be efficient and just use the instrument for which they got the instrumentKey or not allow users to select a default.

@ianbjacobs
Copy link
Contributor

Hi Adrian,

Using your example as a starting point, here is what I had in mind:

  • I install BobPay which allows me to identify a default payment instrument. BobPay explains to me that when I am am presented with the default payment instrument and select it, payment will be super fast with minimal friction.

  • Via Payment Handler API BobPay registers three payment instruments;

    • Blue-when-displayed-as-default with instrument key k1, identified as the default
    • Blue with instrument key k2
    • Green with instrument key k3

    Note that this happens under the hood and the user doesn't have to do anything
    special other than identify that blue is the default. There's a bit of redundancy
    in the registration, but that's a feature.

  • Payment Handler API states that the browser displays either k1 or k2+k3.

Thus:

  • Some details for the blue payment instrument will be common to k1 and k2.
  • Some information could usefully be different, such as the displayed string
  • Because the instrumentKeys differ, the handler knows which one the user selected.
  • The behavior for k1 and and k2 could differ even if the card number (etc.) is the
    same.

I should have provided this example in my post; thanks for prompting it.

Ian

@adrianhopebailie
Copy link
Contributor

That works. So defining a default also means defining a second key for that instrument.

In terms of the API would a design where I don't repeat the same instrument twice be better? Perhaps change first bullet to:

  • The PaymentInstrument dictionary includes an optional attribute: defaultInstrumentKey (string). At most one instrument may have a value for defaultInstrumentKey within the scope of
    PaymentManager.PaymentInstruments or within the scope of a PaymentWallet.

@ianbjacobs
Copy link
Contributor

@adrianhopebailie wrote: "In terms of the API would a design where I don't repeat the same instrument twice be better? "

I intentionally offered that design with the expectation that some information about the instrument might change if used in a default context. If that is never useful, we should consider other options.

Your proposal (a defaultInstrumentKey) does not on its own give you the necessary second key to be able to distinguish "default" from "all." This could be done in various ways. For example, if displayed and selected in a default context, the browser could send the payment an additional piece of data like "defaultSelected" to complement the instrumentKey of the selected instrument.

Summary:

  • It feels like we need two pieces of data.

  • That might take various forms such as "a second key" or "a flag when the default has been displayed and selected"

  • I am not at all wedded to my proposal that involved some duplication. There may also be ways to combine ideas like:

    defaultInstrumentKey ::= instrumentKey
    defaultInstrumentName ::= NameWhenDisplayedAsDefault

It would be good to hear what people think is necessary for a good user experience. Does the payment handler need to specify a special label for the default case, or could the browser do that in a manner consistent across payment apps?

Ian

@adrianhopebailie
Copy link
Contributor

Your proposal (a defaultInstrumentKey) does not on its own give you the necessary second key to be able to distinguish "default" from "all."

It does. The idea would be for the default to have two keys.
To use your example above:

instrumentKey ::= instrumentKeyWhenDisplayedInFullList (Required)
defaultInstrumentKey ::= InstrumentKeyWhenDisplayedAsDefault (Optional - only allowed to be set on one instrument)

@ianbjacobs
Copy link
Contributor

Concretely, one sets the instrumentKey on an Instrument through the PaymentInstruments interface, so we will have to revisit that interface. Today it has these methods:
delete, get, keys, has, set, clear. How do these behave when an Instrument has a "default"
key in addition to its "otherwise" key? In particular, does keys() return the default key?

For example:

  • delete(instrumentKey): works with all keys including default.
  • get(instrumentKey): works on all keys including default
  • has(instrumentKey): works on all keys including default
  • set(instrumentKey, InstrumentDetails): works on all non-default keys
  • setDefaultKey(instrumentKey,defaultKey): Makes the Instrument the default and assigns second key.
  • getDefaultKey(): returns defaultKey if specified, otherwise null
  • keys(): works on all non-default keys. (I exclude the defaultKey so that you can do count(keys()) and determine the number of Instruments.
  • clear(): works on all Instruments

Ian

@tommythorsen
Copy link
Member

I am personally not that fond of the defaultInstrumentKey concept, as I find it a bit strange.

I think that if the payment handler is launched with the instrumentKey property set, it should proceed directly to pay with that instrument (with our without opening a window), or if it is launched without the instrumentKey it should show UI for selecting an existing instrument or configuring a new one.

The browser could present the payment handlers/instruments in such a way as to make sure the user is not put in a situation like Adrian's Scenario 2 above, where the default instrument prevents the user from selecting a different payment instrument. Even though a default instrument is provided by the payment handler, the browser should also give the user a way to invoke the payment handler without this payment instrument. Exactly how this is done is a UX design question, but it would for instance be possible to add a button on the right hand side of the item that represents the payment handler with its default instrument. Something like:

💳 BobPay (Green) ✒️

... where clicking on the pen would invoke the payment handler without instrumentKey set, or clicking anywhere else on the item would invoke the payment handler with instrumentKey set to the key of the default instrument Green.

@ianbjacobs
Copy link
Contributor

@tommythorsen,

I see support for your UX suggestion: when showing default, make it easy to also launch the payment handler in "no default" mode (but without selecting a particular payment instrument to get there).

It is not clear to me how the first part of your comment addresses the original problem statement:
how do you distinguish in the payment handler these two user actions:
Case 1: Browser displays instrument A (the "default") and user selects it.
Case 2: Browser displays all the registered instruments and use selects instrument A.

It may be that the user experience changes between Case 1 and Case 2. How are you informing the payment handler which Case happened?

Ian

@tommythorsen
Copy link
Member

@ianbjacobs My comment does not address the difference between your Case 1 and Case 2 as I didn't see any good reason to do so. In what scenario would this be useful?

Unless we have a really good reason to add this functionality, I suggest that we keep things simple for now. Some way to distinguish between Case 1 and 2 could always be added later.

@jnormore
Copy link
Member

I agree with Tommy here. Unless there's a good reason, it seems like added complexity with little benefit. IMO if the browser wants to default to a selected instrument based on context it has of past usage then that's (probably/maybe?) great added functionality it could provide, the handler could then influence that default indirectly after the first use anyway, but no need to build it into the spec.

@ianbjacobs
Copy link
Contributor

Discussed on 20 June::
https://www.w3.org/2017/06/20-apps-minutes.html#item06

Thanks to @jnormore and @Alyver for volunteering to write up some notes on three use cases:

  1. Browser displays all instruments and user selects 1
  2. Browser displays one instrument for selection (among multiple)
  3. Browser displays indication that there are multiple instruments available, and user action launches payment app. The label used to indicate multiple instruments might look like this (for example): "Visa **1234 and 4 other instruments"

Thanks!

Ian

@adrianhopebailie
Copy link
Contributor

Given the decision to remove "wallets" the spec has become somewhat cleaner wrt this scenario. I wanted to try and summarize where we are (I think).

The remaining challenge is a payment app (origin) that has multiple service workers where the instruments are registered to different SWs.

If a user explicitly selects an instrument then it is clear which SW's request handler will be invoked however if the browser simply presents the origin of the payment app and the user selects this it is unclear what the behavior should be.

Option 1: Invoke on all SW's and handle first response that is provided
This seems technically feasible but is a crappy developer experience.

Option 2: Always invoke the default instrument (and pick a default if the handler hasn't set one)
This seems best but raises the questions we have above. Does the handler behave differently based on what the user was shown?

Proposal

Keep the API simple and intuitive for developers:

  1. Add a setDefault(PaymentInstrument) method to PaymentInstruments
  2. Add a displayedInstruments property to the PaymentRequestEvent that lists the instrumentIds of the instruments shown to the user.

This will provide extra value to the payment handler as it can know explicitly what options the user was given and what they selected.

If the user was presented only with the origin of the payment app (no default presented to the user) this list must be empty. The browser must still select a default and invoke the appropriate SW's event handler and populate the selectedIntrument property. (This seems an unlikely scenrio given @zkoch 's comments. I'd expect the browser to always pick a default and display that to the user)

If the user is presented with one option (a default defined by the handler OR selected by the browser) then only that key should be passed in the displayedInstruments list.

interface PaymentInstruments {
    Promise<boolean>             delete(DOMString instrumentKey);
    Promise<PaymentInstrument>   get(DOMString instrumentKey);
    Promise<sequence<DOMString>> keys();
    Promise<boolean>             has(DOMString instrumentKey);
    Promise<void>                set(DOMString instrumentKey, PaymentInstrument details);
    Promise<void>                setDefault(DOMString instrumentKey);
    Promise<void>                clear();
};

[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                           selectedInstrument;
    readonly attribute sequence<DOMString>                 displayedInstruments;
    Promise<WindowClient> openWindow(USVString url);
    void                  respondWith(Promise<PaymentHandlerResponse> handlerResponse);
};

@rsolomakhin
Copy link
Collaborator

Chrome plans to present the payment handler's name and icon from its web app manifest. Payment handler writers should put different names and icons in /personal/webmanifest.json and /business/webmanifest.json to avoid confusion between the two.

@ianbjacobs
Copy link
Contributor

Hi all,

I just chatted with @adrianhopebailie to walk through his proposal. I found it useful during the discussion to write down the scenarios we have in mind, and illustrate how the two pieces of information (selectedInstrument and displayedInstruments) combine to give payment app developers information they need for a variety of potential user experiences.

Here are the scenarios when the user clicks the the payment app for bobpay.com.

  1. User agent displays "bobpay.com" (with no information about any instruments displayed).
    In this case, selectedInstrument is null and displayedInstruments is empty.
    The payment app could behave in different ways depending on expectations set
    with the user. It might act immediately using the default instrument, or it might display all the
    available instruments.

  2. User agent displays "bobpay.com Visa ending 1234".
    In this case, selectedInstrument is the instrumentKey associated with that card, and
    displayedInstruments is an array with one value (that same instrumentKey).
    The payment app would presumably act immediately on the default instrument.

  3. User agent displays "bobpay.com Visa ending 1234 (more)" where 'more' can be
    activated. The user agent might behave differently when 'more' is activated,
    so let's examine each case:

    a) The user agent displays all of the payment app's instruments, then the user selects
    one. In this case, selectedInstrument is the key associated with that instrument, and
    displayedInstruments is an array with the keys of all instruments displayed by the
    user agent.

    b) The user agent invokes the payment app directly. In this case, selectedInstrument
    is null and displayedInstruments is an array with the key associated with the
    Visa card.

NOTES:

  • All of the scenarios above result in different data reaching the payment app.
    The goal was to ensure that payment app developers could understand the
    "display context" which I think this proposal addresses.

  • There is no proposal here that the browser do 3(a) or 3(b), only that whatever
    the behavior is, the user agent "commits" to sending the two pieces of information
    to the payment app.

  • We should define the behavior of user agents when the payment app has set
    a default instrument and when it has not. I think that when the payment app
    has not specified a default payment instrument, it's ok for the user agent to provide
    one (e.g., "most recently used) as long as it conveys the information to the payment
    app, as Adrian has proposed. If the payment app has specified a default instrument,
    we have to decide whether to require the user agent to display it, or something else
    (e.g., must not display any other instrument, but may display no instrument).

@marcoscaceres
Copy link
Member

marcoscaceres commented Jul 25, 2017

Hhmmm... this can be simplified a lot... Maybe:

interface PaymentInstruments {
    Promise<boolean>             delete(DOMString instrumentKey);
    Promise<PaymentInstrument>   get(DOMString instrumentKey);
    Promise<sequence<DOMString>> keys();
    Promise<boolean>             has(DOMString instrumentKey);
    // Just put the id on the `details`
    // 🚨 Also, this has to return itself - like .set() in a JS Map()  
    Promise<PaymentInstruments>                set(PaymentInstrument details);
    Promise<PaymentInstrument>                setDefault(DOMString instrumentKey);
    Promise<void>                clear();
};

Also, please see how to wire this up to be an async generator with:

for async (const instruments of navigator.paymentInstruments){
    // THIS HAS TO WORK
}

@marcoscaceres
Copy link
Member

Also, you are missing
.values()

For reference, please see:
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map

Properties

  • Map.prototype
  • Map.prototype.size
  • Map.prototype[@@toStringTag]
  • get Map[@@species]

Methods

  • Map.prototype.clear()
    *Map.prototype.delete()
  • Map.prototype.entries()
  • Map.prototype.forEach()
  • Map.prototype.get()
  • Map.prototype.has()
  • Map.prototype.keys()
  • Map.prototype.set()
  • Map.prototype.values()
  • Map.prototype@@iterator

Or maybe we can make this a set:
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Set

@marcoscaceres
Copy link
Member

Also, there how does one get the default one?

@marcoscaceres
Copy link
Member

to integrate async iteration protocol, please see:
https://github.com/tc39/proposal-async-iteration

@marcoscaceres
Copy link
Member

<rant>
Also, you folks should help us finish Payment Request first instead of working on this 😭 ... there is a crap ton of testing and spec work to be done in the other API, which this API completely depends on. It's really frustrating that I can't work also on this one because otherwise the PR API won't get done.

This API risks just adding more stuff to the platform that we might not need, and folks at Mozilla will get upset because a bunch of odd API choices will get made before we have a proper chance to participate.

Can we please stop spec work on this an finish the other API, please!!!!
</rant>

@ianbjacobs
Copy link
Contributor

Hi @marcoscaceres,

Thanks for the input on this issue.

I agree with you that PR API is the top priority, but there are also people interested in ensuring that Web-based payment apps continue to advance, even if it's slow, and even if we have to make changes.

Here are some ideas:

  • Are there other people (e.g,. from Mozilla) who may not be actively implementing or testing PR API and who are interested in helping us with Payment Handler, or at least keeping an eye on it?
  • We are actively seeking more testing resources for PR API. If it would be useful to you, I am happy to schedule a regular slot where, either by phone or chat or otherwise, we do testing project management to make sure we have targets and are reaching them.

Ian

@marcoscaceres
Copy link
Member

even if it's slow,

It's too fast right now.

and even if we have to make changes

I'd still be more in favour of incubation, not standardization. I'd like to see where Chrome's experiment/prototype with PayPal and other partners ends up.

Are there other people (e.g,. from Mozilla) who may not be actively implementing or testing PR API and who are interested in helping us with Payment Handler, or at least keeping an eye on it?

No. There is a team of us tracking this, but we are all actively working on PR API. We don't have the scope to contribute properly to the handlers work and ship PR in a reasonable time in an interoperable manner.

Consider, we have literally no idea today if payment method modifiers even work interoperably. There are no tests and Chrome currently has them disabled. I've no idea if they are supported in Edge and if they will work interoperaby with Firefox.

That should give everyone pause. Like serious pause: because modifiers change the total and can add displayItems.

We are actively seeking more testing resources for PR API. If it would be useful to you, I am happy to schedule a regular slot where, either by phone or chat or otherwise, we do testing project management to make sure we have targets and are reaching them.

Could definitely work: Need people to be proactively writing tests or reverse engineering implementation.

@ianbjacobs
Copy link
Contributor

Noting here that we discuss at the payment apps call today:
https://www.w3.org/2017/07/25-apps-minutes#item03

There is agreement this proposal needs more work and encouragement to discuss here.

Ian

@ianbjacobs
Copy link
Contributor

Closing this issue as there is currently no plan by any browser to implement a default instrument behavior.

Instead we pursued user hint and merged this pull request:
#206

Ian

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants