GithubHelp home page GithubHelp logo

Comments (26)

adrianhopebailie avatar adrianhopebailie commented on August 11, 2024

We can actually separate this issue into two discussions as the API facilitates communication between both the website and the payment app and the website and the payment mediator (user agent/browser).

As such the design restrictions are different.

To illustrate, it may be quite simple for browser vendors to implement a facility whereby the website is exchanging numerous messages with the payment mediator (assumed to be built into the browser). Such a mechanism is suggested in the paymentRequest proposal for getting the shipping address directly from the browser prior to selecting a payment app.

However, extending this to the payment app could be very challenging, especially in the mobile deployment scenario where the payment app will likely be an entirely separate application to the browser and inter-app communication is not well supported.

I would suggest that trying to support a "chatty" conversation pattern between the website and the payment app is not feasible via the API (due to restrictions described above for mobile).

On the other hand supporting such a pattern between the website and browser SHOULD only be done if absolutely required as it adds significant complexity to the API with regards to state management of the payment request (see #64, #36 and #41). (To date the only requirement driving this is the gathering of the shipping address as discussed in #39).

An alternative mechanism for payment apps to communicate with the website during the processing of a payment request is discussed in #76. This mechanism has the benefit of having few standardization requirements (only a standard for sharing a callback URL in the payment request is required). It also offers all of the pros of Option 2 above with none of the cons.

Even if it is not standardized this method is likely to become a de-facto standard as payment app publishers and merchants seek out a mechanism to support use cases like dynamically updated offers (payment requests) based on the user indicating they are loyalty program members, have vouchers/coupons, or wish to make a multi-tender payment.

This suggests that there is very little motivating for a complex conversation pattern in the API.
i.e. We should stick to a simple request/response with no events.

from webpayments.

adrianba avatar adrianba commented on August 11, 2024

I don't understand this issue. The API shape is derived from the capabilities of the API and whether it can be practically implemented. If it can't then we won't get interoperable implementations that allow the spec to proceed.

from webpayments.

adrianhopebailie avatar adrianhopebailie commented on August 11, 2024

The purpose of the issue is to tease out the design constraints of the various deployment scenarios for the browser API, or as you put it:

whether it can be practically implemented.

I think it's fair to say we have established one clear constraint: that a "chatty" conversational pattern between the website and payment app is not possible with the architecture that is currently envisioned on mobile devices (i.e. a payment app is a stand-alone app separate from the browser).

What this implies is that once we are ready to submit a payment request to the payment app we must have gathered all of the data required for the payment app to process the request as the payment app can't make requests back to the website for more data.

The only way this could be achieved is through an out of band mechanism as described in #76 which the majority of the group seem to feel does not need to be standardized yet.

On the other hand, as your proposal demonstrates, a website and the browser can easily exchange numerous messages (prior to passing control to the payment app) but the second design constraint that the issue addresses is whether this is practical given that once the browser has started processing the initial API call it will have taken the focus away from the website.

Since the website has lost focus any callback to the website (whether via events, resolving promises or any other mechanism) freezes the UI in a "waiting" state until the website responds or the call times out.

I don't believe that these calls can be made asynchronously as the use cases suggest user interface updates will be required once the browser receives a response.

Therefore my assertion is that there are two design constraints we must consider:

  1. It will be impossible to implement this API in all deployment scenarios (esp mobile) if the API requires numerous messages to be passed back and forth between the website and the payment app therefor the API should assume that a single request can be passed to the payment app and it will receive a single response.
  2. It is undesirable to lock up the user interface while the browser waits for the website to respond to a callback or event (eg: shipping option selection or shipping address change) therefor the API should use a pattern for making these requests that allows the browser to intelligently manage the flow and where possible avoid website callbacks.

from webpayments.

adrianba avatar adrianba commented on August 11, 2024

Since the website has lost focus any callback to the website (whether via events, resolving promises or any other mechanism) freezes the UI in a "waiting" state until the website responds or the call times out.

The PaymentRequest API does require that you prevent a user from accepting a payment while the web site is given an opportunity to do something that might change the price (such as recalculating shipping costs) but that doesn't require a "frozen" UI.

I don't believe that these calls can be made asynchronously as the use cases suggest user interface updates will be required once the browser receives a response.

I think it is essential that they will be made asynchronously. Making synchronous calls that result in I/O is a design anti-pattern that we need to avoid.

Your assertions are founded on a particular design for payment apps that we wouldn't implement in our platform so this wouldn't be a practical limitation. We haven't heard other implementers yet say they couldn't support this but that was the point I was making: we have to see what they say for their platforms rather than making general assertions about what might be the case.

Today the PaymentRequest API proposal has an event driven interface for getting to the final price before allowing delegation to another app. This supports your proposal that "...once we are ready to submit a payment request to the payment app we must have gathered all of the data required for the payment app to process the request as the payment app can't make requests back to the website for more data." But it doesn't require a move away from the conversation pattern that occurs prior to that point.

So I don't understand the need for an abstract issue about conversational pattern.

from webpayments.

msporny avatar msporny commented on August 11, 2024

The only way this could be achieved is through an out of band mechanism as described in #76 which the majority of the group seem to feel does not need to be standardized yet.

I wouldn't say that - I'd say that the majority of the group doesn't understand that not standardizing this means that there are no 3rd party payment apps that aren't tightly coupled. For example, actual native applications running on a mobile device. So, while I'm sure Google, Microsoft, and Apple would have no problem writing a native payment app for their mobile platforms, I doubt a non-multi-billion-dollar-multinational would have an easy time doing such a thing.

So, just to be clear - I wouldn't view this as indifference - some of us have just been sucked into more pressing conversations (like the extensibility discussion).

from webpayments.

adrianhopebailie avatar adrianhopebailie commented on August 11, 2024

So I don't understand the need for an abstract issue about conversational pattern.

The title is misleading. As I said in my follow up there are two issues here, the conversational pattern between the website and the payment app and the conversational pattern between the website and the browser.

I agree that the requirements contribute to the choice of pattern but there are also design constraints that define which patterns are actually implementable. The need to discuss the conversational pattern stems from the need to acknowledge and consider these constraints.

The discussion also identifies which design pattern the requirements are surfacing which should enable us to pick a well-known pattern that addresses these rather than rolling our own.

The PaymentRequest API does require that you prevent a user from accepting a payment while the web site is given an opportunity to do something that might change the price (such as recalculating shipping costs) but that doesn't require a "frozen" UI

The browser is waiting for a response from the website before it will allow any further processing to be done. This implies that this is a synchronous process (simply using events as the request delivery mechanism doesn't make it asynchronous). What can the UI do other than freeze and show a "waiting" icon while it waits for the website to respond?

I think it is essential that they will be made asynchronously. Making synchronous calls that result in I/O is a design anti-pattern that we need to avoid.

I agree but what you are proposing is simply an asynchronous mechanism to acheive a synchronous processing flow. All that does is ensure that nobody locks up the processing during some I/O but the UX is still locked up.

This is because the use cases that are currently proposed are inter-dependent. The business rules of these interactions define the order in which they are performed and also define what user choices can be made at what stages. The user may be able to pick the shipping method before or after the address but only the payee will know this.

If the choice of shipping method is dependent on the choice of shipping address then there is a synchronous flow of control from the browser to the website, after the user has selected their address, to get the valid methods for that address. The browser UI can't do anything until the website returns a response except ask the user to wait.

Your assertions are founded on a particular design for payment apps that we wouldn't implement in our platform so this wouldn't be a practical limitation.

Can you elaborate on what aspects of the payment app design won't be implemented by Microsoft? I'd argue that designing any API without understanding the capabilities of the payment app is pointless so we should clarify this first.

Today the PaymentRequest API proposal has an event driven interface for getting to the final price before allowing delegation to another app. This supports your proposal that "...once we are ready to submit a payment request to the payment app we must have gathered all of the data required for the payment app to process the request as the payment app can't make requests back to the website for more data." But it doesn't require a move away from the conversation pattern that occurs prior to that point.

I agree. I am not proposing that pattern HAS to change. My assertion is that his pattern is less desirable than one which doesn't lock up the UI. If locking up the UI is understood by everyone to be an acceptable limitation we are placing on implementors then that's fine but let's have that conversation.

the PaymentRequest API proposal has an event driven interface for getting to the final price

I agree with Dave's comments in #41 about the use of events. Requesting information from the website is not an event. It is a synchronous request that requires a response from the caller. As discussed the browser must necessarily stop processing further inputs from the user and wait for a response. This is not the type of interaction that events were designed for.

I don't believe that Promises are the correct mechanism for this either. Perhaps callbacks are a better model?

A variation on the checkout proposal:

// Checkout.request takes a callback as its second parameter the signature of all 
// callbacks is the same and includes an object carrying the data provided by the 
// user via the browser.

// Callbacks return an object that is passed to the browser as input to the next 
// request.

var checkout = new Checkout();
checkout.request('shippingAddress', getShippingOptions)
  .request('shippingOption', buildPaymentRequest)
  .request('payment', processPaymentResponse) // Calls the payment request api
  .start();

from webpayments.

rsolomakhin avatar rsolomakhin commented on August 11, 2024

I view events and callbacks as quite similar. Calling these functions "callbacks" makes more sense. I agree with @adrianhopebailie.

from webpayments.

dlongley avatar dlongley commented on August 11, 2024

Note that these callbacks would actually need to return Promises that resolve to the objects given to the browser as input to the next request, not the objects themselves.

The callback approach is one I also considered but it felt more like constructing a node.js API vs. a Web one. Maybe that's not such a big deal.

EDIT: Actually, the callbacks could return either (a Promise that resolves to the required object or the required object) if we want to go that way.

from webpayments.

rsolomakhin avatar rsolomakhin commented on August 11, 2024

@dlongley Why should the callbacks return promises?

from webpayments.

dlongley avatar dlongley commented on August 11, 2024

@rsolomakhin -- so that the implementations of those callbacks can run asynchronous code. For example, consider that the getShippingOptions function may need to make a call to the merchant site's server to retrieve the shipping options for a given shipping address. If the callback does not return a Promise, then this doesn't work. You could alternatively pass another callback function (node.js style for async programming) to the getShippingOptions function, but the preferred pattern on the Web is to use Promises.

from webpayments.

dlongley avatar dlongley commented on August 11, 2024

@adrianba opened a PR on paymentrequest that looks like it may address this issue (by mirroring what FetchEvent does):

WICG/historical-paymentrequest#50

I need to look more closely, but that approach does appear like it could resolve some of the control flow issues.

from webpayments.

dlongley avatar dlongley commented on August 11, 2024

I'll add -- I don't think that approach fully solves problems like having shipping options depend on shipping address choices. So I think there are still some flow issues to figure out, but we at least have another pattern we could potentially use to improve the API and try and get it where it needs to be.

from webpayments.

zkoch avatar zkoch commented on August 11, 2024

@dlongley can you clarify this bit: "I don't think that approach fully solves problems like having shipping options depend on shipping address choices"?

Are you saying that shipping options should not depend on shipping address choices? Because I think that is, in many instances, a requirement.

from webpayments.

dlongley avatar dlongley commented on August 11, 2024

@zkoch,

Are you saying that shipping options should not depend on shipping address choices? Because I think that is, in many instances, a requirement.

No, I'm not saying that -- and I agree it's a requirement. I'm saying I think we potentially want to integrate some aspects of the event approach (with the FetchEvent-based change of calling a function w/a Promise to do the update) and the request approach. Ideally, the API would also allow browsers to easily add new types of information that could be requested during Checkout -- without the need to spin up a new WG and modify the spec. That's one of the aims of using the request approach -- the other is for managing what information a merchant needs and the order they need it in.

from webpayments.

dlongley avatar dlongley commented on August 11, 2024

@zkoch,

Here's a first quick attempt at combining the approaches:

var checkout = new Checkout();
checkout
  .send('paymentItem', paymentItems)  // send line item estimate to UA
  .request('shippingAddress')         // request shippingAddress from UA
  .addEventListener('shippingAddressChange', shippingAddressChanged)
  .addEventListener('shippingOptionChange', shippingOptionChanged)
  .start()                            // start the checkout UI
  .then(finishCheckout);              // checkout UI has collected the info

function shippingAddressChanged(event) {
  var checkoutDetails = event.checkoutDetails;

  if(!isAddressAcceptable(checkoutDetails.address)) {
    return checkout.cancel('We cannot ship to your address.');
  }

  // send updated payment items and request shipping option selection;
  // each function here can return a Promise
  // Note: could this be `checkout.send` and `checkout.request` again
  // instead of `event.send` etc.?
  event
    .send('paymentItem', getPaymentItems(checkoutDetails))
    .request('shippingOption', getShippingOptions(checkoutDetails.address));
}

function shippingOptionChanged(event) {
  event.send('paymentItem', getPaymentItems(event.checkoutDetails));
}

function getPaymentItems(checkoutDetails) {
  // post checkout details to shipping calculation endpoint and return
  // updated payment items in a Promise
  return fetch('/calc-shipping', {
    method: 'POST',
    body: JSON.stringify(checkoutDetails)
  }).then(function(res) {
    return res.json();
  });
}

function getShippingOptions(checkoutDetails) {
  // determine shipping options from checkoutDetails.shippingAddress
  // ...

  return Promise.resolve(shippingOptions);
}

function finishCheckout(checkoutDetails) {
  // asynchronously create a payment request from the checkout details
  customCreatePaymentRequest(checkoutDetails).then(function(paymentRequest) {
    return checkout.finish(paymentRequest);
  }).then(function(acknowledgement) {
    // handle acknowledgement from payment app
  });
}

from webpayments.

dlongley avatar dlongley commented on August 11, 2024

My concern is that if we don't define (at least eventually) much of this flow as separate low-level APIs then we'll be prescribing far too much.

As the checkout detail interdependencies increase, so will the management complexity increase ... leading merchants to want to design and control their own checkout flows. This is further reason to ensure that we layer this stuff -- create low-level APIs that people can assemble in JS libraries. I do think there's room for a simple high-level checkout API for the "80%" case, but we should be clear that it is composed of lower-level APIs (or can eventually be, where the first one is the API that just handles getting an extensible payment request message to a payment app which will return a payment acknowledgement).

from webpayments.

adrianhopebailie avatar adrianhopebailie commented on August 11, 2024

@adrianba 's latest PR is definitely an improvement. Thanks also to @jakearchibald for his input.

I still don't like using events to effectively make a request (as opposed to signal an event) but I will concede that this pattern, combined with the use of a promise as the return value, appears to be gaining popularity for asynchronous comms across an API boundary on the Web platform.

If the event object has an updateWith method that takes a promise (as it's now designed to do) the website can at least respond immediately.

SIDENOTE: I wonder if the updateWith(Promise<PaymentDetails> d) signature could be less restrictive so that the website could simply pass in the result of calling fetch?

I would still like to see some description in the spec of what should happen if the event is emitted and there are no listeners and include some timeout behaviour.

I interpreted @dlongley 's comment to be alluding to a related question about inter-dependancy between events:

I don't think that approach fully solves problems like having shipping options depend on shipping address choices

If the next step that the browser UI flow must take is dependent on getting a response from the website (i.e. the user has picked an address and the shipping address changed event has been emitted and the browser is waiting for a list of shipping options from the website) then:

  1. What is the behaviour of the browser UI which it seems must remain disbled for input while it waits? Are we happy to leave this to the implementors to figure out?
  2. What is the behaviour of the browser if the website never resolves the promise and much of this should we define in the spec?

from webpayments.

adrianhopebailie avatar adrianhopebailie commented on August 11, 2024

I would still like to see some description in the spec of what should happen if the event is emitted and there are no listeners and include some timeout behavior.

I thought about this a little and had an idea.

When the website initiates the checkout process by calling show() would it be a valid response for the browser to respond immediately with an error if the PaymentRequest is configured to emit events and there are no listeners for those events?

I'll confess to still not being convinced that we should ask developers to configure the PaymentRequest to gather a shipping address in one place (via the configuration object) and then define how they want to handle the event somewhere else.

from webpayments.

adrianba avatar adrianba commented on August 11, 2024

I would still like to see some description in the spec of what should happen if the event is emitted and there are no listeners and include some timeout behaviour.

If there a no event listeners then nothing happens and that's perfectly reasonable. In that instance there is nothing for a timeout to do.

I'll confess to still not being convinced that we should ask developers to configure the PaymentRequest to gather a shipping address in one place (via the configuration object) and then define how they want to handle the event somewhere else.

They don't have to handle the event if they don't want to.

from webpayments.

adrianhopebailie avatar adrianhopebailie commented on August 11, 2024

If there a no event listeners then nothing happens and that's perfectly reasonable.

Not if the next step that the browser must perform is dependent on the response from the website.

This is exactly why the event pattern isn't ideal for this use case (requesting data from the website). It breaks if there are no listeners and the event emitter is depending on there being one that will respond to the event.

from webpayments.

adrianba avatar adrianba commented on August 11, 2024

Not if the next step that the browser must perform is dependent on the response from the website.

There is no such dependency. If you don't call updateWith then there is no next step for the browser.

from webpayments.

dlongley avatar dlongley commented on August 11, 2024

@adrianhopebailie,

Not if the next step that the browser must perform is dependent on the response from the website.

So I think the question is whether or not we can design this API so it works like the other APIs that use this event + Promise pattern.

So, to understand how this pattern is used in other APIs, let's look at the ServiceWorker API. When using respondWith in this API, you're modifying something that will always happen. When a request is sent out, you'll always get a response. Installing an event handler simply allows you to modify that response by instructing the fetch call to respond with something else when it's time to respond (and when the response you've prepared is ready, this is the Promise-based part).

In more detail, my understanding is that the interaction with the FetchEvent in ServiceWorker API works like this:

  1. A call to fetch is made by a webpage.
  2. A fetch event is dispatched.
  3. An event handler in a ServiceWorker may optionally call respondWith to modify the response. If respondWith is called, then fetch's default behavior is disabled. Only one handler may call respondWith, if another call is made an InvalidState error is triggered.
  4. Once event dispatching has completed, if respondWith was called, it will wait for its Promise to settle and use its response. If it wasn't, the default response will be used. So a response will happen whether or not respondWith was called, it's merely a modifier.

With the Checkout API, we want to be able to do things like make shipping options depend on the shipping address selected. What we're exploring essentially works like this:

  1. A Checkout object is created by a webpage.
  2. A call to send the line item estimate is made on the Checkout object. This call essentially just sets "options" that will be read once the checkout is started.
  3. A call to request shipping address is made on the Checkout object. Again, this call is like setting options as it simply "indicates" that a shipping address is requested. It won't do anything until the checkout is started.
  4. A call to start checkout is made on the Checkout object. This returns a Promise that will settle once the user clicks "Buy".
  5. The browser UI opens and sees the line item estimate and displays it. It sees that shipping was requested, so it knows to show the user a shipping address selection UI. The "Buy" button will be disabled.
  6. The user selects a shipping address and a shippingAddressChanged event is dispatched.
  7. An event handler may optionally call request to request shipping option selection along with a list of possible shipping options (or a Promise that resolves to such a list).
  8. Once event dispatching has completed, if request was called, it will wait for its Promise to settle and then display the shipping options in the UI, keeping the "Buy" button disabled. Otherwise, there's nothing to wait on and the "Buy" button will be enabled.
  9. The user selects a shipping option and a shippingOptionChanged event is dispatched.
  10. An event handler may optionally call send to send updated checkout details such as new calculated line items that include, eg: shipping, tax. Again, this information can be sent as a Promise, to enable the website to generate this information asynchronously. Similarly, request can also be called to request other information at this point (that depended on shipping option selection).
  11. Once event dispatching has completed, if request was called, it will wait for its Promise to settle and then display the UI for whatever was requested this time, keeping the "Buy" button disabled. Otherwise, there's nothing to wait on and the "Buy" button will be enabled.
  12. The user clicks "Buy" and the Promise for checkout.start resolves.
  13. The webpage generates a payment request message and passes it to checkout.finish.
  14. The browser UI shows the Payment App selection UI to complete the purchase.

Again, I think everything can progress forward regardless of whether or not extra calls to request are made. In writing that down, however, I do see at least one problem. If the user selects a shipping address that has N shipping options -- and then they change that shipping address selection, we want to be able to clear those shipping options from the UI. I think that can be easily solved with a call to send('shippingOption', null) or send('shippingOption', theOnlyOne) when handling the shipping address change.

from webpayments.

dlongley avatar dlongley commented on August 11, 2024

So it looks like this API would behave very similarly to the ServiceWorker API. The browser UI is essentially just waiting for the right moment to enable the "Buy" button. If you don't request anything, it will enable the "Buy" button immediately. If you react to events that are generated from user input by requesting more information, then the "Buy" button will remain disabled and the browser will be responsible for rendering UI elements to collect that information. Only once you stop asking for more information will the "Buy" button become enabled post a user fulfilling whatever requests were previously made.

from webpayments.

jakearchibald avatar jakearchibald commented on August 11, 2024

Regarding high level APIs vs low level ones, I strongly recommend doing the lower-level now then creating the higher level based on observed usage.

The places we went high-level early in service worker have pretty much all come back to bite us.

from webpayments.

dlongley avatar dlongley commented on August 11, 2024

@jakearchibald,

Regarding high level APIs vs low level ones, I strongly recommend doing the lower-level now then creating the higher level based on observed usage.

The places we went high-level early in service worker have pretty much all come back to bite us.

This is my fear here as well. At a minimum, I recommend we define the low-level APIs that we know we're chartered to do and that won't conflict with future work elsewhere. If we are to produce something higher-level because we believe there is a strong demand for it, we should design it such that it could be composed out of lower-level APIs, including making use of those that we are ready to create today.

So that means if we do a "Checkout" API, it should rely upon a lower-level API (that focuses on simply taking a payment request message and returns a payment acknowledgement) and it should also be designed such that it is just one possible assembly of other future lower-level APIs for acquiring information such as (verified) shipping addresses and so forth.

from webpayments.

msporny avatar msporny commented on August 11, 2024

Partially migrated to w3c/payment-request#51.

@adrianhopebailie, I'm trying to migrate issues to the WG specs that would eventually address the issue. This is your issue, close it if you feel it is now being handled better in the browser payment API spec.

from webpayments.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.