w3c / webpayments

The document repo for the Web Payments Working Group
https://github.com/w3c/webpayments/wiki
Other
257 stars 62 forks source link

Should we expose status change events in the browser API? #41

Closed msporny closed 8 years ago

msporny commented 8 years ago

The paymentRequest API exposes a number of events that a payee site may listen to while the payment request is being processed:

http://wicg.github.io/paymentrequest/specs/paymentrequest.html#events

The Web Payments CG Browser API specification does not expose any such events.

Are there use cases where listening to payment request processing events are useful to the payee's software?

msporny commented 8 years ago

Are there use cases where listening to payment request processing events are useful to the payee's software?

I'm asserting that the current events, which are shippingaddresschange and shippingoptionchange, should be done through a different verifiable claims API. If these are removed, there are no events that are left in the requestPayment API, which would raise the question if there are /any/ events that are worth listening to.

adrianhopebailie commented 8 years ago

This will likely be influenced by the proposal from @rsolomakhin actioned here: https://github.com/WICG/paymentrequest/issues/28

There seems to be an implied requirement for the payment app to be able to communicate with the calling application (website) prior to the former returning the payment response.

Right now this is being accomplished through events in the paymentRequest spec and through HTTP callbacks in the CG proposal.

We should consider if there are use case(s) that justify this requirement first and then, if we feel these exist, decide what the mechanism will be for facilitating this.

zkoch commented 8 years ago

This is, of course, strongly related to #39. Given that I am a strong advocate of collecting shipping information, yes, I think this is necessary (and critical). I'm not sure the value of this issue being separate from what is being discussed in #39.

adrianhopebailie commented 8 years ago

The shipping use case is a strong motivator for creating some mechanism for the payee website to be able to update the payment request based on user input.

The question is, should this line of communication go all the way to the payment app or is this locked down to the mediator. In this case it is the mediator that is gathering the user input (shipping option selection) and then notifying the website. i.e. The user hasn't yet selected a payment app.

Is that the intention or should the payment app have a way to raise events that the website consumes and can act upon to send the payment app data?

dlongley commented 8 years ago

The current event-based API for status changes does not allow enough freedom for websites that install handlers to perform asynchronous operations. For example, when a shipping address changes, what if the merchant website needs to perform an async request to obtain updated pricing information?

This could be mitigated by adding another method to the state machine or the event that the merchant has to call from their event handler once they are ready for the next state change to occur. Unfortunately, this is a bit complex and runs afoul of the direction the Web platform is heading with respect to promises.

Perhaps a better suggestion would be to avoid events all together and allow the merchant website to provide hooks that may return promises. This way, once the UA has performed an action, such as collecting an address, it can call the hook and wait for its promise to resolve before continuing.

However, even then, I feel that this API is failing to separate concerns and is therefore too prescriptive at its high-level. In my view, the use of events to monitor state changes in current API reminds me of XMLHttpRequest. XMLHttpRequest has been much maligned for its complexity and use of events to track state changes (among other things):

For more than a decade the Web has used XMLHttpRequest (XHR) to achieve asynchronous requests in JavaScript. While very useful, XHR is not a very nice API. It suffers from lack of separation of concerns. The input, output and state are all managed by interacting with one object, and state is tracked using events. Also, the event-based model doesn’t play well with JavaScript’s recent focus on Promise- and generator-based asynchronous programming.

https://hacks.mozilla.org/2015/03/this-api-is-so-fetching/

Because of these problems, a new API (fetch) has been created that uses Promises and Streams to produce something that is both more low-level, more composable, less complex, and easier to use.

I fear we're repeating the same mistake with this API. We're using events to track state changes and it's suffering from a separation of concerns (eg: collection of address information from performing a payment). I don't feel like we're building a modern, low-level, composable API, rather we're constructing an old-style API where we are ignoring the lessons that others who are standardizing better APIs in the Web Platform have not. Again, see fetch or ServiceWorkers vs. AppCache as a perfect example of this.

Note: Service Workers win over previous attempts in this area such as AppCache because they don't make assumptions about what you are trying to do and then break when those assumptions are not exactly right; you have granular control over everything.

https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API

(There are plenty of other good articles talking about the mistakes made with APIs like AppCache and XMLHttpRequest).

zkoch commented 8 years ago

For example, when a shipping address changes, what if the merchant website needs to perform an async request to obtain updated pricing information?

This is actually the exact use case we're trying to support. :) When it's all said and done, I think we'll limit the amount of time a merchant has to call the update() method after an event is emitted. Still thinking about that and working out details, though.

Thanks for other comments as well. Need a bit more time to process and think about them. Will circle back.

dlongley commented 8 years ago

When it's all said and done, I think we'll limit the amount of time a merchant has to call the update() method after an event is emitted.

I believe this can be solved much more elegantly by making the collection of address information a separate operation. This would also allow the merchant to collect this information at a time that works for them and their flow. As it stands today, if a merchant wants or needs to collect this information at a different time (eg: earlier in the shopping/checkout process), they will not be able to use this API to do so.

Thanks for other comments as well. Need a bit more time to process and think about them. Will circle back.

Sure!

dlongley commented 8 years ago

@adrianhopebailie,

The shipping use case is a strong motivator for creating some mechanism for the payee website to be able to update the payment request based on user input.

Another way of looking at this is that the current API allows the payment request to be sent prematurely.

dlongley commented 8 years ago

@zkoch,

I'm sure they are quite busy, but if possible it may be a good idea to request feedback from @domenic and @jakearchibald.

adrianhopebailie commented 8 years ago

@dlongley said:

I believe this can be solved much more elegantly by making the collection of address information a separate operation.

and

Another way of looking at this is that the current API allows the payment request to be sent prematurely.

:+1: to this separation of concerns and changing the shape of the API so that the payment request data is not required at step 1 (currently in the paymentRequest constructor).

I also think @dlongley 's earlier comments about the current shape of the API are very valid and justify a separate issue or should be repeated here: https://github.com/w3c/webpayments/issues/36

As I see it, the case for allowing the browser to help gather shipping address is premised on the fact that this is data many browsers already have (from form auto-complete systems) or will gather each time a user makes a purchase. The goal is to leverage this and allow the UA to gather the shipping data on the merchants behalf as part of the "check-out" flow, using the same UI that will be used for the payment, thereby making the flow slicker and hopefully reducing abandonment.

The current shape of the API (a single API call that both submits the payment request data and attempts to gather shipping data) is based on this goal in an effort to create a mechanism by which the UA can reuse the same dialogue to gather the shipping data and get the user's payment app selection.

I propose that it is possible for UAs to offer the user what appears to be a seamless flow (gathering shipping data and then processing the payment) using separate API calls from the website.

I made a crude example of that here which surfaces a few ideas about how this might be acheived: https://github.com/w3c/webpayments/issues/39#issuecomment-165488995

In the example I used the Credential Management API which I think in the long term is the correct way to do this. I am open to suggestions as to how this same thing might be achieved in the short term without compromising the payment API.

Would it make sense for there to be an API function like:

navigator.payments.getShippingAddress(...);

which becomes a shortcut to this in the long term:

navigator.credentials.get({"type": "shippingAddress", ... });
adrianhopebailie commented 8 years ago

A snip from the TAG guidance on using promises: https://www.w3.org/2001/tag/doc/promises-guide#legacy

Appendix: Legacy APIs for Asynchronicity

Many web platform APIs were written before the advent of promises, and thus came up with their own ad-hoc ways of signaling asynchronous operation completion or failure. These include:

(other examples removed for brevity)

  • XMLHttpRequest’s send method, which triggers onreadystatechange multiple times and updates properties of the object with status information which must be consulted in order to accurately detect success or failure of the ultimate state transition

If you find yourself doing something even remotely similar to these, stop, and instead use promises.

jakearchibald commented 8 years ago

If it's something that can happen multiple times, and it isn't a stream (chunks of data that form a whole), then events are the right primitive.

So XHR's "load" example is a poor example, it should be a promise. XHR's "progress" events should be a stream.

However, address change sounds like an event to me. It can happen multiple times. It isn't a stream. Sure the developer is likely to need to respond async, but we handle that in service worker using waitUntil:

self.addEventListener('sync', event => {
  event.waitUntil(
    doSomeStuff()
  );
});

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match('/')
  );
});

Both waitUntil and respondWith take a promise. This means the API knows that an action completes, when it fails, and what the eventual value is, which is useful in the case of respondWith.

Sounds like shippingaddresschange would work in a very similar way.

zkoch commented 8 years ago

Thanks @jakearchibald. @adrianba just actually submitted a pull request doing this exact thing for PaymentRequest events: https://github.com/WICG/paymentrequest/pull/50

Would be great if you could check it out if you have a free moment.

dlongley commented 8 years ago

@jakearchibald,

Both waitUntil and respondWith take a promise. This means the API knows that an action completes, when it fails, and what the eventual value is, which is useful in the case of respondWith.

I agree. This is the pattern we were looking for. Thanks!

msporny commented 8 years ago

Closing this as both spec proposals ended up supporting events for things like shipping address change.