-
Notifications
You must be signed in to change notification settings - Fork 63
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
What is the appropriate conversational pattern for the API? #55
Comments
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 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. |
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:
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:
|
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 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. |
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). |
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 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 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.
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.
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.
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(); |
I view events and callbacks as quite similar. Calling these functions "callbacks" makes more sense. I agree with @adrianhopebailie. |
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. |
@dlongley Why should the callbacks return promises? |
@rsolomakhin -- so that the implementations of those callbacks can run asynchronous code. For example, consider that the |
@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. |
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. |
@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. |
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 |
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
});
} |
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). |
@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 SIDENOTE: I wonder if the 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:
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:
|
I thought about this a little and had an idea. When the website initiates the checkout process by calling 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 |
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.
They don't have to handle the event if they don't want to. |
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. |
There is no such dependency. If you don't call updateWith then there is no next step for the browser. |
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 In more detail, my understanding is that the interaction with the
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:
Again, I think everything can progress forward regardless of whether or not extra calls to |
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. |
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. |
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. |
i.e. Should the API be an open channel for communication between the Website and Payment App or should it simply accept a request and return a response and all other comms must be done out of band?
This question has been raised implicitly in a few other issues such as #41 and #39.
It is also surfaced as a requirement of the API due to the discussion around shipping, tax and line item details.
In the case of the browser API the options are:
Option 1
The API accepts a payment request and focus moves to the Payment App. The Payment App will return a payment response to the Website and the Payment App will close.
Pros
Cons
Option 2
The API accepts a payment request and focus moves to the Payment App. Certain actions in the Payment App will cause a message to be returned to the Website during which time the Payment App UI will remain in focus but will be in a non-responsive or waiting state until the Website responds to that message and the Payment App can continue processing based on the Website's response. Finally the Payment App will return a payment response to the Website and the Payment App will close.
Pros
Cons
The text was updated successfully, but these errors were encountered: