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

Revisiting payment app filtering #96

Closed
adamroach opened this issue Jan 23, 2017 · 64 comments
Closed

Revisiting payment app filtering #96

adamroach opened this issue Jan 23, 2017 · 64 comments

Comments

@adamroach
Copy link
Contributor

The current spec includes a function that a payment app can optionally register for itself (canHandle() in the existing setManifest() method) for the purpose of determining whether the payment app is applicable to the payment method and its filters. This is one of several mechanisms that have been discussed to date.

@marcoscaceres correctly points out that, as specified, the current mechanism is somewhat problematic, in that we're assuming basically a "pickling" of this function for use in future contexts in a way that's not obviously supported by ES.

In practice, when I proposed this approach (and, to be clear, it was my second-favorite choice out of options on the table), what I had in mind was something very similar to how PAC files operate, both from a persistence and execution environment perspective. While there are probably ways to specify canHandle() in a way that is more palatable (e.g., by having it point to an external file that is evaluated independently), there is some question as to whether doing so is worth the effort it is likely to take.

The alternate approach is to more concretely define a payment filtering algorithm based on a generic understanding of the filter parameter passed as part of the PaymentRequest.

I'm opening this issue to re-kindle the discussion regarding these two potential approaches, in light of the newly highlighted difficulties we are likely to encounter. Of course, alternatives approaches beyond the two I describe are welcome as well.

@jakearchibald
Copy link

jakearchibald commented Jan 24, 2017

If this cannot be determined from data the browser already has, and needs to execute code in the origin, a service worker event is the best fit.

@tommythorsen
Copy link
Member

I think that changing this into an event might not be such a good idea privacy-wise. In a regular event handler, there's nothing stopping the service worker from logging all of the payment request information and passing it back to the back-end. That would let an information-greedy payment provider see all of the user's potential payments -- even the ones that don't ultimately end up in that particular payment app.

The intention of the currently specified mechanism is that the registered canHandle() function should be a "pickled", "pure" function ("pure" meaning that the function does not rely on anything other than the input parameters, and does not have any side-effects).

I'm not actually a great fan of the current canHandle() mechanism, but since I don't have a better suggestion, I think it's acceptable, as long as we can show that it's implementable. If not, then I would like to propose that we drop the whole canHandle() mechanism.

Since this has turned out to be very difficult to specify in a way that fulfills everyone's needs, maybe it would be better if we postponed this mechanism to version 2 of the specification? That would require that we revert some changes to the basic-card specification, and move back to having individual method IDs for the various types of cards, with all the problems that entails, but maybe those problems are easier to tackle.

@marcoscaceres
Copy link
Member

But the only info the SW gets is "someone wants to know if you can handle this" - not who or what or where they are trying to buy. So the info is not useful beyond "lots of sites wonder if this particular payment method is supported by me".

@marcoscaceres
Copy link
Member

In fact, there may be no intent to buy anything - because a site intimates a canMakeActivePayment() potential long before any actual payment happens (if it happens at all!). The browser knows the registered methods of the app, so it could strip out the ones that don't match.

@jakearchibald
Copy link

@tommythorsen can you give an example of a question that the browser cannot answer with data it has, but can be answered by executing code?

@ianbjacobs
Copy link
Contributor

@marcoscaceres wrote "The browser knows the registered methods of the app, so it could strip out the ones that don't match."

We asked ourselves "who should do the filtering, the browser or the payment app?"
There are pros and cons to each approach. If browsers do it, payment apps don't have to,
which is a good thing.

However, we expect to see a number of payment methods in use, each with its own filtering mechanism, and if we rely on the browser, then we are not likely to see those implemented. Therefore, we have favored asking payment apps to implement the filtering.

Ian

@adrianhopebailie
Copy link
Contributor

The browser knows the registered methods of the app, so it could strip out the ones that don't match.

@marcoscaceres This filtering of the Payment Request to a new object that represents a subset of the data is exactly what the current spec does and that you have insisted is a bad idea and should be taken to the TAG for review. I'm confused now as to what you're actually proposing?

@ianbjacobs
Copy link
Contributor

@adrianhopebailie,

I think there is conflation of two topics:

  1. Whether some (as we currently anticipate) or all of the payment request data should be fed to a payment app.
  2. Who should be evaluating whether a merchant filter for a given payment method matches what a given payment app's filter? Should it be the payment app (as we currently anticipate) or the browser?

Ian

@adrianhopebailie
Copy link
Contributor

@ianbjacobs I disagree

@marcoscaceres said, in the context of whether the data passed to the SW is private or not:

The browser knows the registered methods of the app, so it could strip out the ones that don't match.

This implies he is talking about the data passed to the app which he is also advocating should be the exact same request object passed to the browser

@adamroach
Copy link
Contributor Author

@jakearchibald

If this cannot be determined from data the browser already has, and needs to execute code in the origin, a service worker event is the best fit.

To be clear, this code must not execute in the origin. It needs to execute in its own unique origin.

@jakearchibald
Copy link

@adamroach can you give an example of a question that the browser cannot answer with data it has, but can be answered by executing code?

@marcoscaceres
Copy link
Member

marcoscaceres commented Jan 25, 2017

As I'm sucking at using natural language, I'm proposing this:

bank.com / index.html

const { paymentManager: pm } = navigator.serviceWorker;
pm.methods.set("visa-4756", {
  name: "Visa ending ****4756",
  methods: ["basic-card"],
  icons: [visaIcons],
});

merchant.com / checkout.html

const methodData = [{
    supportedMethods: ["basic-card"],
    data: {
      supportedNetworks: ['aFamousBrand', 'aDebitNetwork'],
      supportedTypes: ['debit']
    }
  }, {
    supportedMethods: ["bobpay.com"],
    data: {
      merchantIdentifier: "XXXX",
      bobPaySpecificField: true
    }
  }];
const request = new PaymentRequest(method, details, options);
const canDoIt = await request.canMakePayment();

bank.coms/service-worker.js

// We only get ["basic card"] methods, not bobpay.com!
addEventListener("canmakepayment",  ev => {
  ev.canMakePayment(new Promise(resolve => {
    let canDoIt = false;
    for(const {supportedNetworks, supportedTypes} of ev.methods){
      if(supportedNetworks.includes("aFamousBrand") && supportedTypes.includes("debit")){
        canDoIt = true;
      }
    }
    // Could do other checks async...
    resolve(canDoIt);
  });
});

@marcoscaceres
Copy link
Member

(the above should actually destructure "method.data", but you get the idea.... the point is that bank.com can do that checks it needs need against the matching payment methods sent from merchant.com - without spying on merchant.com's bobpay's stuff)

@ianbjacobs
Copy link
Contributor

Hi @marcoscaceres,

Thanks for the code!

First, bank.com is doing the evaluation of the merchant filter. That is consistent with what we've sought.

Second, it seems that the browser is giving bank.com's service worker only the data associated with the payment method(s) supported by bank.com. That's also consistent with what we've sought. However, it suggests that a subset of methodData is given to the payment app, and I had the impression from another discussion you did not want to do that. Perhaps I have misunderstood one suggestion or the other. But our current preference is for the browser to give the service worker only that information relevant for the payment methods it supports.

When we first started talking about service worker handling of filters, people observed the following:

  • The general flow works like this: the merchant calls payment request API, the browser
    determines which payment apps match the accepted payment methods, the browser
    displays the matching ones, the user selects one, and only at that moment does the
    payment app gets the payment request data.

  • In your proposal, the flow works like this: the merchant calls payment request API, the browser
    determines which payment apps match the accepted payment methods, then the browser
    sends those payment apps the filter data for those payment apps and the payment app
    evaluates the filter, the browser displays the matching ones (taking into account the
    responses from the payment apps), the user selects one, and then the selected payment
    app gets the rest of the payment request data.

Thus, your proposal reveals information about the transaction to all payment apps with
matching payment methods, before any one of those payment apps has been selected
by the user.

People expressed privacy concerns with this approach. That's why we pursued a design where
the payment app registers a function that the browser can evaluate outside of the
payment app's knowledge.

I hope that sheds light on how we ended up with what we have.

I look forward to more discussion to work through this,

Ian

@adrianhopebailie
Copy link
Contributor

I like the approach of using an event (I think I have heard consensus that the group i happy with this change).

What we need to maintain though is the requirement that the event handler is executed in a "sandbox" of some sort that prevents the data passed in being stored or sent over the network.

As @ianbjacobs points out, this is partly based on privacy concerns, but also because this needs to return quickly as it likely impacting UI (the set of apps being presented to the user)

@tommythorsen
Copy link
Member

can you give an example of a question that the browser cannot answer with data it has, but can be answered by executing code?

The browser is not able to answer questions regarding the contents of the data fields in PaymentMethodData and PaymentDetailsModifier dictionaries. The structure of the contents of these fields are proprietary, and only known by the payee (the merchant) and the payment provider (the one that owns the payment app).

An example of what the contents of the data field might be, can be found in the Basic Card Payment specification. Here, information about the supported networks ("visa", "amex", "mastercard", etc) and the type of card ("credit", "debit", etc) is supplied in the data field.

Is the canHandle() mechanism an absolute and fundamental requirement that we can't do without? As I've said above, I'm not a great fan of this, and I feel that it shouldn't be necessary for the merchant to ask this question, as long as he can supply a fallback option in the form of a recommended payment app to ensure that the user isn't presented with a dead end in the payment request UI.

@marcoscaceres
Copy link
Member

marcoscaceres commented Jan 25, 2017 via email

@tommythorsen
Copy link
Member

tommythorsen commented Jan 25, 2017

@adrianhopebailie

What we need to maintain though is the requirement that the event handler is executed in a "sandbox" of some sort that prevents the data passed in being stored or sent over the network.

While this would conceptually take care of the privacy concerns, I feel that this might be too hard to implement in a secure way. Are there any examples of similar things in existence today, or are we treading new ground here? If it's the latter, then I am apprehensive about this.

@marcoscaceres

But the only info the SW gets is "someone wants to know if you can handle this" - not who or what or where they are trying to buy. So the info is not useful beyond "lots of sites wonder if this particular payment method is supported by me".

The SW is installed into a browser by a web site that might have the user logged in. It also regularly opens client windows which presumably will prompt for user login. I think that the SW does know "who".

As for "what" and "where", these depend on the contents of the opaque data fields in the payment request. Although this is out of our control, I think it is likely that most of these will contain something that lets the payment provider identify the merchant. My gut feeling tells me that the SW can know "where". If the merchant is narrow enough in scope, the payment provider can probably also get some ideas about "what", just by knowing "where".

@marcoscaceres

In fact, there may be no intent to buy anything - because a site intimates a canMakeActivePayment() potential long before any actual payment happens (if it happens at all!). The browser knows the registered methods of the app, so it could strip out the ones that don't match.

It is likely that canMakeActivePayment() will be called while loading the web page, in order to construct a "Buy" button. If my assumptions above, about the payment provider's ability to infer "who" and "where" are correct, then this does not make things any better at all. In fact, this might make a payment provider able to track every visit a user makes to any page that uses payment requests.

@marcoscaceres
Copy link
Member

marcoscaceres commented Jan 25, 2017 via email

@tommythorsen
Copy link
Member

Is the canHandle() mechanism an absolute and fundamental requirement that we can't do without?

I believe it is. The Service Worker or object that the Service Worker gets representing the PaymentRequest must be able to handle all the method calls: .canMakePayment(), .abort(), .updateWith(), etc.

Well, we could remove .canMakePayment() too...

I don't think we should do the recommended payment app thing - the merchant can recommend whatever they like (or fall back to basic card).

I'm not sure I follow your argumentation, but falling back to basic card is certainly also an option. I think that for the sake of kickstarting the payment app ecosystem, though, recommended payment apps has the potential to work very well.

@jakearchibald
Copy link

@tommythorsen

The browser is not able to answer questions regarding the contents of the data fields in PaymentMethodData and PaymentDetailsModifier dictionaries. The structure of the contents of these fields are proprietary, and only known by the payee (the merchant) and the payment provider (the one that owns the payment app).

I think I get it, but could you provide a quick example (with code)?

I understand the thing about the proprietary structures, but what isn't clear to me is how the other origin would answer that without any access to origin data or communication.

It kinda feels like the recommended apps issue - too much time is being spent discussing the mechanism rather than the bigger picture.

Are there any examples of similar [sandboxing] in existence today, or are we treading new ground here? If it's the latter, then I am apprehensive about this.

I think it's new ground. There are worklets from the houdini project, which may be similar. However, are we sure this solves the problem, if the code has no access to network or storage?

@tommythorsen
Copy link
Member

tommythorsen commented Jan 25, 2017

@jakearchibald

I think I get it, but could you provide a quick example (with code)?

Ok, I'll give it a shot. But instead of using the boring basic card payment method, I'm going to use a hypothetical visa checkout payment app. I've taken some of the property names and values from the visa checkout specification, so this is not entirely unrealistic. Here's the code which the merchant uses to create a payment request:

var methodData = [
    {
        supportedMethods: [ "https://secure.checkout.visa.com/pay" ],
        data: {
            apikey: "9898df897df87df987df98d7f987df",
            xPayToken: "xv2:" + timestamp + ":" + hashString,
            settings: {
                payment: {
                    cardBrands: [ "VISA", "AMEX" ],
                    acceptCanadianVisaDebit: false
                }
            }
        }
    }
]
var details = {
    total: {
        label: "Total",
        amount: { currency: "USD", value: "100.00" }
    },
    displayItems: [
        {
            label: "Blue suede shoes",
            amount: { currency: "USD", value: "100.00" }
        }
    ],
    shippingOptions: [
        {
            id: "free",
            label: "Free shipping!",
            amount: { currency: "USD", value: "0.00" }
        }
    ],
    modifiers: []
}

var request = new PaymentRequest(methodData, details);
request.show()

Everything inside methodData.data is proprietary visa checkout stuff, which the browser knows nothing about. A hypothetical canHandle() method provided by the visa payment app, which the user has installed in his browser, could look at the supported cardBrands and the other information in methodData.data and compare this with the cardBrands that the user has previously registered with visa checkout and see if there is a match. If not, then maybe the function should return false.

@adamroach
Copy link
Contributor Author

@jakearchibald --

I think it's new ground.

From a spec perspective, that's probably true. As I mention above, though, the execution environment I'm proposing is identical to the execution environment PAC files run in: no network access and a unique origin. This has been implemented in browsers for decades.

@jakearchibald
Copy link

jakearchibald commented Jan 25, 2017

@tommythorsen

and compare this with the cardBrands that the user has previously registered with visa checkout and see if there is a match

This sounds like it'd require access to origin storage, no? Could you show the code for that bit too?

@tommythorsen
Copy link
Member

@jakearchibald

This sounds like it'd require access to origin storage, no? Could you show the code for that bit too?

Yeah, either it needs access to storage, or the function needs to be individually constructed for the user. I have not made any attempts to explore this subject any further than this, so your attempt to implement would be as good as mine. Maybe one of the actual proponents of this mechanism would be more interested in writing some example code for this?

@tommythorsen
Copy link
Member

@adamroach

[...] the execution environment I'm proposing is identical to the execution environment PAC files run in: no network access and a unique origin. This has been implemented in browsers for decades.

While it has been implemented in browsers for decades, a quick google search for "pac file attack" makes me doubt that this is proof that it works, security-wise. On the first page, I get the following hits:

Further reading on the subject suggests that the way to stay secure w.r.t. PAC files boils down to "for god's sake, don't let PAC files from a non-trusted source onto your system", rather than "don't worry, the browser's sandbox will keep you safe". Do we think we can make our function more secure than this? My gut feeling says that it's not possible to do this in a way that's absolutely and demonstrably bulletproof.

@jakearchibald
Copy link

jakearchibald commented Jan 26, 2017

The design of this (if it's necessary) depends heavily on the kind of access it needs to other things.

@adamroach can you pick up where @tommythorsen left off? Can you write some code that shows what a developer may need to write in a canHandle callback? Something that shows a decision being made that the browser couldn't make itself. Just use the current as-specced API - it'll be enough to get an idea of what's needed.

I imagine there are a few ways to solve this, and some are defined by specs already, but we shouldn't be diving into the solution until we fully understand the problem.

@adamroach
Copy link
Contributor Author

@tommythorsen -- Those attacks are uniformly due to the role that PAC files play (controlling where all web traffic goes), rather than the environment they run in.

@jakearchibald
Copy link

Discussing PAC files is redundant. There are existing specs that could be used for originless computation. There's also the possibility of creating something new.

Sorry to sound like a stuck record but… clarifying the problem and its requirements should be done before searching for a solution.

Can we do that first?

@adrianhopebailie
Copy link
Contributor

@rsolomakhin, I think we may need to discuss this step:

  • If matchingFiltersNumber is equal to len(dataKeys), i.e., all filters match:
    • Add method to matchingMethods.

A note from previous discussions on this topic for context, an app that wishes to "farm" merchant data or always be presented to the user is simply going to claim support for as many payment methods as it knows about with the bare minimum filters. That way it increases its odds of being selected to handle a payment and can try to enroll the user for a requested method once it is invoked. (i.e. It fakes support to get selected and then tries to "wing it" once it is, by supporting basic card as a fallback). Worst case scenario it gathers data about the merchant and it's accepted payment methods.

One way to counter this is to only allow the request to specify a "wildcard". i.e. A merchant can submit a request with the method bobpay.com and no filters and this will match a payment app that is registered to handle the bobpay.com payment method but does have additional filters specified.

Another possibility is that browsers intelligently order options presented to the user based on how often that option has resulted in a failed request in the past?

@rsolomakhin
Copy link
Collaborator

An app that wishes to "farm" merchant data or always be presented to the user is simply going to claim support for as many payment methods as it knows about with the bare minimum filters.

There's no 100% guaranteed way to avoid this, but the user agent can use own signals to detect such apps and penalize them, somehow. I don't see how this affects the spec, though.

Another possibility is that browsers intelligently order options presented to the user based on how often that option has resulted in a failed request in the past?

We plan to do this.

@adamroach
Copy link
Contributor Author

@adrianhopebailie --

One way to counter this is to only allow the request to specify a "wildcard". i.e. A merchant can submit a request with the method bobpay.com and no filters and this will match a payment app that is registered to handle the bobpay.com payment method but does have additional filters specified.

I'll note that the algorithm I sketch out has this exact property: if a merchant specifies a filter, then the payment app must have a matching capability. If the payment app has a capability that the merchant hasn't requested, that makes no difference.

Also, I think it muddies the conversation to think of these as "filters" on the app side -- they're capabilities. The merchant is specifying a filter that demands certain capabilities of payment apps in order for those apps to match. If you think of them in that way, it makes it much easier to see how they should interact.

@adrianhopebailie
Copy link
Contributor

Also, I think it muddies the conversation to think of these as "filters" on the app side -- they're capabilities. The merchant is specifying a filter that demands certain capabilities of payment apps in order for those apps to match. If you think of them in that way, it makes it much easier to see how they should interact.

+1

@marcoscaceres
Copy link
Member

marcoscaceres commented Jan 31, 2017

It's still not clear to me if "native" payment handlers will have more capabilities to determine if .canMakePayment() can respond with "true" than the filtering here allows. That is, if a native (e.g., Android Pay, Apple Pay, Microsoft Wallet, or a native app installed from an app store) payment handler can do its .canMakePayment() support by:

  • performing a network request.
  • talking to a peripheral over USB, BlueTooth, NFC, or whatever.
  • access a database that checks something related to the user.

Then we are doing the web a disservice by not providing an analogous set of capabilities (via service workers).

However, if the answer to .canMakePayment() is that native payment solutions only do the string matching, then filtering is fine.

@rsolomakhin
Copy link
Collaborator

Apple Pay has canMakePayments and canMakePaymentsWithActiveCard. Android Pay has isReadyToPay. These methods precede our enlightened conversations, however, and therefore should not be taken as gospel, IMHO.

@marcoscaceres
Copy link
Member

canMakePaymentsWithActiveCard... This method asynchronously contacts the Apple Pay servers as part of the verification process.

I rest my case.

@adamroach
Copy link
Contributor Author

I'm a bit confused. Every time Nick Shearer has spoken on this topic, he's indicated that these Apple Pay methods are very limited, and effectively provide the application hints (not promises) about whether there is some kind of payment instrument registered. It doesn't even allow the requestor to indicate which kind of card they're asking about -- it's just "is Apple Pay activated on this device in a way that it could, in theory, for some card, and I don't know which one, possibly but not certainly, make a payment?"

And I'm pretty confident that doing so does not involve any network interaction whatsoever.

@tommythorsen
Copy link
Member

Reading the comments above, I sense that there is a bit of confusion around the two different pieces of functionality that can be used to check the ability to make a payment. Let me just try to make things clear:

  1. canHandle() / "config based filter": This is the mechanism which we should be discussing in this issue. We may implement it as code or as config, but the purpose is the same: This mechanism allows the mediator to ask a payment app whether it can handle a given payment method. The mediator will most likely make use of this mechanism in two cases:
    1. as part of calculating the answer to PaymentRequest.canMakePayment().
    2. to figure out which payment apps/methods to display when the merchant has called PaymentRequest.show().
  2. The second mechanism is the PaymentRequest.canMakePayment() function. While it will most likely call into the canHandle() mechanism above, it is not exactly the same thing. This function is likely to be called while the page is loading, as part of constructing a pay button on the merchant's page.

The speed requirements on these two mechanisms are different. Since PaymentRequest.canMakePayment() most likely is called during page load, it's not a big deal if it talks to a server and returns an answer slowly, since it happens at a time when everything else is slowly being constructed as well. canHandle() on the other hand, may be invoked by the PaymentRequest.show() function, at a time when user interface is being shown to the user, and a list of payment options is being populated before the eyes of the user. This situation calls for a snappier response.

The Apple Pay methods mentioned above, are equivalent to the PaymentRequest.canMakePayment() function, not to the canHandle() function. The fact that these Apple Pay methods might be asynchronous and may talk to the server, does not influence the speed requirements on the canHandle() filtering mechanism.

@marcoscaceres
Copy link
Member

@adamroach, wrote:

And I'm pretty confident that doing so does not involve any network interaction whatsoever.

This is the case for canMakePayments(), which is synchronous. But not the case for canMakePaymentsWithActiveCard(), as Apple's own docs state.

@nickjshearer? Can you kindly confirm if Apple Pay makes a network request when .canMakePaymentsWithActiveCard() is called, as per the canMakePaymentsWithActiveCard()documentation states.

@tommythorsen, wrote:

The speed requirements on these two mechanisms are different. Since PaymentRequest.canMakePayment() most likely is called during page load, it's not a big deal if it talks to a server and returns an answer slowly, since it happens at a time when everything else is slowly being constructed as well. canHandle() on the other hand, may be invoked by the PaymentRequest.show() function, at a time when user interface is being shown to the user, and a list of payment options is being populated before the eyes of the user. This situation calls for a snappier response.

This is imposing UX/UI ideas on the spec: consider, the list of payment providers could be incrementally shown or become enabled as each becomes ready:

screenshot 2017-02-07 14 43 17

In the above, "wallet.com" is not ready yet for whatever reason.

The Apple Pay methods mentioned above, are equivalent to the PaymentRequest.canMakePayment() function, not to the canHandle() function. The fact that these Apple Pay methods might be asynchronous and may talk to the server, does not influence the speed requirements on the canHandle() filtering mechanism.

If the above holds, then I'd be in support of @rsolomakhin's proposal

@nickjshearer
Copy link

I usually abstain from the Payment Apps discussion since I'm not taking an active role in driving it, but say my name three times and like a titular movie ghost I will be summoned. Anyway, if it will help you reach consensus here's how Apple Pay discovery in Safari currently works.

In Apple Pay today, we have two functions. One is canMakePayments(). This is analogous to a hardware check - does the user have hardware capable of using the feature. In many cases you could figure this out already from the user agent. Anybody can call this method.

The second, more complex function, is canMakePaymentsWithActiveCards. This tells you that the user both has capable hardware and an active payment instrument on that device. What it doesn't tell you is anything about that instrument other than it exists - indeed, it's possible that this function returns true but the user still be unable to make the payment on your site (perhaps, for instance, your site doesn't take American Express, but that's the only instrument the user has on their device).

So when @adamroach says...

these Apple Pay methods are very limited, and effectively provide the application hints (not promises) about whether there is some kind of payment instrument registered.

...he's correct, and that's by design to protect the privacy of the user. Indeed, the user may have chosen to turn this discovery off in Safari entirely in which case it will always simply return true.

But as @marcoscaceres suggests and our documentation confirms:

This method asynchronously contacts the Apple Pay servers as part of the verification process. You can only call this method if you want to default to Apple Pay during your checkout flow, or if you want to add Apple Pay buttons to your product detail page.

We perform some checks that the domain is currently validated for Apple Pay (for security merchants who take Apple Pay must prove ownership of their domain), and to make sure site owners aren't mis-using or abusing the API. So yes, it's an asynchronous call (it actually returns a promise)[1].

Like I said at the start, I'm going to abstain from any kind of recommendation, but I will say that it's a very real case that some payment apps will need to perform asynchronous work to establish whether they're available or not. Sometimes that might be for security - like Apple Pay - other times, though, it might not even be a network request, it might be something like talking to a connected piece of hardware.

[1]: For those interested you can see what's actually going on between these two calls here: https://trac.webkit.org/browser/trunk/Source/WebCore/Modules/applepay/ApplePaySession.cpp#L472

@ianbjacobs
Copy link
Contributor

ianbjacobs commented Feb 9, 2017

Hi all,

I'd like to summarize where I think we are on the filtering topic and
propose a way forward.

Summary:

  • For the smoothest user experience, we want the user agent to show
    only those payment apps for selection that have a good chance of
    success.

  • One mechanism we have come up with to increase the chances for
    success for merchants to declare under what conditions they accept
    a given payment method. We have been calling the data that the
    merchant provides to the user agent a "filter." Lately there was
    a suggestion that a better word might be "capability". In this
    summary I use "capability".

  • Capability data is thus payment method specific.

  • For basic card v1 we have settled on the capability supporting two
    pieces of information: supported networks and card type.

  • We also see the usefulness of capabilities in two other draft
    payment method specifications: tokenized card, credit transfer.
    Thus, we know of three specifications where capability matching
    would be useful.

  • Payment apps also need to be able to declare their capabilities in
    a manner consistent with the merchant's expressions.

  • When are these capabilities taken into consideration? Who compares
    merchant and payment app capabilities? There seem to be two
    operations where capabilities need to be taken into consideration:

    • show() (section 3.2 of PR API)
    • canMakePayment() (section 3.4 of PR API)
  • Originally the payment apps task force proposed that the user agent
    (mediator) compare the merchant and payment app capabilities. We cited
    several advantages to this approach, such as:

    • Reducing the cost of payment app development
    • Reducing the cost of payment method development (since
      payment methods could define capability matching using this small
      bit of common syntax).
  • However, the browser makers indicated they would not likely support
    a generic capability syntax, so the payment apps task
    force resolved to figure out how to have the payment apps take the
    merchant capabilities as input and provide a boolean response back
    to the user agent.

  • For what follows, it's useful to write down the payment app
    flow that I think is generally agreed upon:

    • Merchant calls PR API
    • UA computes payment method intersection
    • UA displays matching payment apps
    • User selects a payment app
    • UA hands data to the selected payment app

    Notice in this flow that no payment app has knowledge that a
    transaction is taking place (and no access to transaction data)
    prior to user selection.

  • For capability matching, one idea was to include it at the
    phase of computing payment method intersection:

    • Merchant calls PR API
    • UA computes payment method intersection, in part
      by calling each payment app with merchant capability data
      and selecting only those that respond "true"
    • UA displays matching payment apps
    • User selects a payment app
    • UA hands data to the selected payment app

    However, this raised privacy issues since every payment app would
    wake up at every transaction, prior to selection by the user.

  • To address this privacy issue, the payment app task force
    contemplated an approach where each payment app could register a
    capability matching function to the user agent (we called this
    "canHandle()). That function would be executed without payment app
    knowledge in some constrained environment (e.g., no network
    access).

  • Marcos (and I think others) indicated that providing a function
    would be problematic. (Detailed rationale omitted here.)

  • Since then discussion on this thread has returned to the idea that
    the user agent could perhaps do the capability matching. AdamR and
    Rouslan both wrote proposals ([1], [2]). Each one amounts to
    limited string set matching.

  • Discussion has also included this question: during canMakePayment()
    and show(), why not query the payment app and let the payment app
    do more powerful things (e.g., make network calls)? My understanding
    of the primary reason not to allow this has to do with the privacy
    concern noted above: we do not want every payment app to know about
    every payment request prior to selection by the user.

  • Note that, once selected by the user, a payment app can do powerful
    things and elect to handle or not handle a payment request. This is
    how we responded to Roy's question about whether payment apps could
    elect to not accept payment requests from some origins. Nick
    Shearer wrote, "it's a very real case that some payment apps will
    need to perform asynchronous work to establish whether they're
    available or not."

Given what I've heard so far, here's my proposal:

  • To achieve an improved user experience in some common cases, we
    will define a small, generic syntax for basic capabilities. The syntax
    will support string set matching by the user agent. (Details to be worked out;
    see below.) The syntax could be used in any payment method.

  • It is a privacy requirement that payment apps not be invoked for
    basic capability matching prior to selection by the user.

  • For more powerful capability checking (e.g., requiring
    asynchronous network calls), payment apps can do so after
    selection by the user. (We have also discussed in the task force not
    preventing user agents from responding to a failure from a payment
    app by immediately showing alternative payment apps.)

  • We ask Rouslan and AdamR to work together and bring a combined
    proposal back to the task force.

  • We seek browser maker support for the combined proposal.

  • We ask AdamR to spec out what changes would be needed in PR API
    to support basic capability matching. This would likely include:

    • The capability syntax and a matching algorithm.
    • How the constructor changes to allow the merchant
      to provide capability matching data.
    • Where in the algorithms to mention application of the
      capability matching algorithm
  • I propose that initially this information be written in a new
    document, and that we will then make a decision how (and whether)
    to incorporate into PR API and reference from payment method
    specifications and the payment app API.

Ian

[1] #96 (comment)
[2] #96 (comment)

@adrianhopebailie
Copy link
Contributor

One mechanism we have come up with to increase the chances for success for merchants to declare under what conditions they accept a given payment method. We have been calling the data that the merchant provides to the user agent a "filter." Lately there was a suggestion that a better word might be "capability". In this summary I use "capability".

I believe the proposal is that a merchant defines "filters" and an app defines "capabilities".

@adrianhopebailie
Copy link
Contributor

Mentioning @adamroach and @rsolomakhin to invite their input

@marcoscaceres
Copy link
Member

marcoscaceres commented Feb 10, 2017

@ianbjacobs, great summary. Thank you. There might be a third option:

  • Given the set of capabilities, adding a boolean attribute or method on PaymentRequest that behaves the same as Apple's synchronous canMakePayment() method - for example, .canPay() - could also be an option. That stops all the conflation, and let's us express the semantics of all this properly - while allowing .canMakePayment() to be handled in the service worker.

@ianbjacobs
Copy link
Contributor

@marcoscaceres,

I'm sorry but I'm not clear on what the third option would be. Could you say a bit more? Thank you!
Ian

@marcoscaceres
Copy link
Member

marcoscaceres commented Feb 10, 2017

@ianbjacobs see #96 (comment) - basically what Nick describes there (trying to avoid summoning him again, so not putting in his username :)).

We have the same requirements: with the "capabilities" in place, the question ("is it capable?") can be answered without needing to go wake up the service worker - however, it will need to hit the Payment Manager's Payment Methods database, which might require IO (so this still needs to be a promise, I think - tho the value could be cached, so could still be fast).

On the other hand, "can I make an 'ACTIVE' payment?" needs to go do fancy things - like talk to hardware, network request, whatever)... and in the case of show(), the "is it capable?" question can also be answered quickly - before we spin up the service worker to actually handle the payment request.

I'm sorry but I'm not clear on what the third option would be.

In the the form of an API:

//During checkout form construction
// this would be fairly fast
if (await request.isCapable()) {
  // Build checkout form - show fancy buttons, but maybe disable
  // some of them until request.canMakePayment() actually confirms we can do stuff. 
  fancyPaymentButtons.forEach(button => button.disabled = true);
  if (await request.canMakePayment()) {
    // Ok, let's actually enable 
    fancyPaymentButtons.forEach(button => button.disabled = false);
  }
}

@marcoscaceres
Copy link
Member

(updated the above, to ask "is it capable?" instead... hopefully that's more clear)

@marcoscaceres
Copy link
Member

And... visualized:

screenshot 2017-02-10 15 28 37

@ianbjacobs
Copy link
Contributor

At the 14 Feb task force call [1] there was consensus to adopt the proposal I wrote above [2]. Adam will write up the parts for the payment app spec; Rouslan will write up the parts for the payment request API. There was consensus that this proposal intends to address a limited set of use cases for capability matching BEFORE user selection of payment app. For more powerful capability matching, there was consensus that the payment apps themselves should handle that once launched.

Ian

[1] https://www.w3.org/2017/02/14-apps-minutes#item03
[2] #96 (comment)

@rsolomakhin
Copy link
Collaborator

See w3c/payment-request#420

adamroach added a commit to adamroach/webpayments-payment-apps-api that referenced this issue Feb 20, 2017
ianbjacobs pushed a commit that referenced this issue Feb 21, 2017
* Proposed resolution to #95 and #96

* Fixing a couple of nits
ianbjacobs pushed a commit that referenced this issue Mar 18, 2017
* Proposed resolution to #95 and #96

* Fixing a couple of nits

* Conflict merge

* Changes to resolve issues #99, #109, #111
and to flesh out Instrument/Wallet APIs.
These incorporate example code from Marcos'
Payment Handler thumbnail document.
@ianbjacobs
Copy link
Contributor

Closing this issue as we are progressing as follows:

  • PR API includes provisions for UAs to implement capability matching
  • The Payment Handler spec will provide capability information and we can open a new issue about specifying that.

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

9 participants