w3c / payment-request

Payment Request API
https://www.w3.org/TR/payment-request/
Other
489 stars 135 forks source link

Making this API work with HTML Forms #330

Closed marcoscaceres closed 7 years ago

marcoscaceres commented 7 years ago

This is a follow on from #326... the ability to use this API with HTML forms seems limited, which strikes me as quite odd given that this API just collects form data. In particular, web applications that are loaded on UAs that don't support this API will need to use HTML forms.

Additionally, PaymentResponse needs to be validated in the same way that one would validate a HTML Form. And, in most cases, PaymentResponse, needs to be POSTed by the developer as FormData to a server for verification.

Thus, it feels deficient that I can't easily do the following (given we have extensive form validation and form-related primitives in the platform):

// If the UA doesn't support the API, then use this flow:
HTML form -> user fills out data -> form.checkValidity() -> form.submit();

// If the UA does support the API, then the flow is:
(hidden) form -> PayRequest -> PayResponse -> form -> form.checkValidity() -> form.submit(); 

Going from a PaymentResponse to populating a form is not overly traumatic. But could be made much more coherent. Additionally, reusing more of the existing form infrastructure could significantly reduce the API surface.

marcoscaceres commented 7 years ago

Basically, the missing piece is being able to "hydrate" a form with a payment request (or more generally with form data).

Ideally:

// if paymentResponse is or inherits from FormData
paymentForm.fillWith(paymentResponse);

//Then we end up in form nirvana 
if( form.checkValidity() ) { 
  form.submit();
} else {
  // fix form issues....
} 
rsolomakhin commented 7 years ago

Does it make sense to have a form that accepts Android Pay fields, for example?

marcoscaceres commented 7 years ago

On 24 Nov. 2016, at 10:17 pm, Rouslan Solomakhin notifications@github.com wrote:

Does it make sense to have a form that accepts Android Pay fields, for example?

Sure, I don't see any problem. Thats just a feature of the payment method. If you look at that example you can see the result just gets copied and POSTed for verification. It's consequential that it's JSON. But my argument stands: the payment response is just form data.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub, or mute the thread.

zkoch commented 7 years ago

I think I'm a bit confused by what "form data" means in this context. Does anything that can go into a JSON object constitute "form data"?

I don't think it's valid to compare this with "UAs that don't support this API" and their need to fall back to forms. By falling back to forms, they're limiting the payment methods that are available. For example, native-only forms of payment (e.g. Android Pay, Samsung Pay, etc) wouldn't be available. Put another way, there is no "pure form" equivalent of these forms of payment. As we get into more complicated forms of payment, this will continue to hold true.

Doesn't this also require having a Form for every form of payment that you support? That seems quite unfortunate to me.

adrianhopebailie commented 7 years ago

I am with @zkoch on this. I'm not sure I see the use case here beyond card payments.

I'd expect that for websites that are posting the PaymentResponse to a server they'll simply POST the JSON in the body of a new request. I think this issue is implying that POSTing application/json is less desirable than POSTing application/x-www-form-urlencoded data?

marcoscaceres commented 7 years ago

@zkoch, wrote:

I think I'm a bit confused by what "form data" means in this context. Does anything that can go into a JSON object constitute "form data"?

Sorry for not being clear, form data means this API: https://xhr.spec.whatwg.org/#formdata

I don't think it's valid to compare this with "UAs that don't support this API" and their need to fall back to forms.

Consider, Firefox will support this on Desktop, but we don't have any plans right now to support this on mobile... let's say, for arguments sake we don't ship on mobile until 2019 or later. I don't know what Chrome's plans are to support this on Desktop - but I imagine your desktop users will need a fallback too.

From that perspective, it's totally valid to say that a UA won't support this API, as I know for a fact that we don't plan to ship this on mobile.

By falling back to forms, they're limiting the payment methods that are available.

Yes, which is fine. It's what you want, really because at the very least one can support "basic card". Or just manual form input.

For example, native-only forms of payment (e.g. Android Pay, Samsung Pay, etc) wouldn't be available.

They won't be available on desktop anyway (at least, not yet). We will be lucky if we even get ApplePay on Mac in non-Apple browsers. Or we will be dependent on plugins.

There are millions of people who will still need to pay the old fashion way on Desktop - but they too should have a great experience.

Put another way, there is no "pure form" equivalent of these forms of payment. As we get into more complicated forms of payment, this will continue to hold true.

I haven't seen any evidence of this yet - because at the end of the day, the payment response is either a token+some name/value pairs or the details of a credit card (both of which require the web app to verify those by POSTing that form data somewhere).

I'm probably missing something tho. Can you help me understand where it gets more complicated than the above?

===

@adrianhopebailie, wrote:

I am with @zkoch on this. I'm not sure I see the use case here beyond card payments.

A lot of people will continue to pay with cards. Cards are not going away. Their usage may actually increase (specially with dynamically-generated, time-bombed, exact value cards). For example: https://twitter.com/producthunt/status/802897292390866945

I'd expect that for websites that are posting the PaymentResponse to a server they'll simply POST the JSON in the body of a new request. I think this issue is implying that POSTing application/json is less desirable than POSTing application/x-www-form-urlencoded data?

This has limited relationship to how it's actually POSTed - and it's not about giving preference to one format over another. FormData is just a representation of the collection of the data as it relates to a form. That data can then be transformed to JSON or whatever.

Thus, FormData is a primitive to represent the data. However, we would need to find a way to transform FormData into JSON by working with @annevk and friends from HTML.

adrianhopebailie commented 7 years ago

If I understand the argument for this correctly then I think it would be more appropriate for the payment method specific details from the response to be FormData only if the payment method is basic card.

i.e. We can spec this in the Basic Card Payment Method Spec

if(paymentResponse.methodName == "basic-card"){
    cardPaymentForm.fillWith(paymentResponse.details);

    if( cardPaymentForm.checkValidity() ) { 
      cardPaymentForm.submit();
    } else {
      // fix form issues....
    } 
}
marcoscaceres commented 7 years ago

@adrianhopebailie, can you help me understand why it doesn't apply to all types? Basically, I'm wondering why ".details" can be collapsed into FormData?

Look at the API:

interface PaymentResponse {
    serializer = {attribute};

    readonly attribute DOMString       methodName;
    readonly attribute object          details;
    readonly attribute PaymentAddress? shippingAddress;
    readonly attribute DOMString?      shippingOption;
    readonly attribute DOMString?      payerName;
    readonly attribute DOMString?      payerEmail;
    readonly attribute DOMString?      payerPhone;

    Promise<void> complete(optional PaymentComplete result = "unknown");
};

All the attributes there can just be collapsed into FormData to the point where all you need is:

interface PaymentResponse : FormData {
   Promise<void> complete(optional PaymentComplete result = "unknown");
};
marcoscaceres commented 7 years ago

@adrianhopebailie see also the long form proposal.

adrianba commented 7 years ago

It's fairly easy for a developer to construct a FormData object and fill it with whatever they want including taking data from a PaymentResponse. I've not personally heard feedback before that this is needed and I'm reluctant to make this change at this stage for what might be a small convenience for a few developers that can be handled with a JavaScript function.

marcoscaceres commented 7 years ago

It's fairly easy for a developer to construct a FormData object and fill it with whatever they want including taking data from a PaymentResponse.

Absolutely, but that's kinda missing the point. We've spec'ed a crazy complex object hierarchy that could be collapsed down into a multi-map structure (FormData or the like). Additionally, the interface we've created has fairly limited serialization options (just JSON) and it's not very extensible.

I've not personally heard feedback before that this is needed and I'm reluctant to make this change at this stage for what might be a small convenience for a few developers that can be handled with a JavaScript function.

This is not really about a small convenience. Consider the three main things the current API doesn't address (or maybe you can tell me how it addresses the following):

  1. Existing web store fronts that are already using forms.
  2. Validation of the Payment Response (where 1 already has form validation - via form.checkValidity()).
  3. The site expects the form to be submit()ted somewhere.

Note again the the above applies in all cases where a browser does not support the API - where a form needs to always be used as a fallback (basically 99% of all websites rely on forms to achieve this today). There's almost 2 decades of form validation machinery in browsers and in libraries which we are effectively not making use of: this strikes me as a bit of an oversight in the design of this API, particularly into how it integrates with the existing web.

If we do choose to keep the current complex object hierarchy, we should perhaps consider instead adding .json() and .formData() methods instead?

zkoch commented 7 years ago

Marcos, it seems as if you're getting caught up on the idea of "web sites currently use forms to collect credit cards, so we should optimize for that." This is what requestAutocomplete was, and we decided that was insufficient for our needs. Chrome was also the only browser to implement, even after a number of years.

A few quick answers to your questions:

Existing web store fronts that are already using forms.

Yep, and they will continue to have to for the foreseeable future. At least, until we can rid the world of basic card payments. :) This itself is not a strong case for making forms the backbone of every payment response, the responses of which can be very varied and quite complex.

Validation of the Payment Response (where 1 already has form validation - via form.checkValidity())

I'm no expert here, but what does a validity check for a proprietary payment response look like? What about for a Stripe token? Or an Apple Pay response? Or a SEPA credit transfer?

It seems to me that validation checks for anything other than basic card are not feasible on the client. In addition to that, what happens if client-side validation fails? How does a user fix a network token?

The site expects the form to be submit()ted somewhere.

Perhaps. We can all agree the payment data ultimately needs to be sent somewhere, but that somewhere is quite varied. It could be Braintree's client-side JavaScript API, where you don't submit() you client.tokenizeCard(), for example.

But these issues aside for the moment, I think the larger point is that the time for such a large change has passed. If this is a path the WG wants to go down, we should consider how we can add this later in a backwards compatible way.

This does seem reasonable, though:

If we do choose to keep the current complex object hierarchy, we should perhaps consider instead adding .json() and .formData() methods instead?

Would this work by allowing a user to do something like, paymentResponse.toFormData() or similar?

marcoscaceres commented 7 years ago

Marcos, it seems as if you're getting caught up on the idea of "web sites currently use forms to collect credit cards, so we should optimize for that."

That's possible, yes 😸 Apologies, I don't meant to derail this too much.

I'm no expert here, but what does a validity check for a proprietary payment response look like? What about for a Stripe token? Or an Apple Pay response? Or a SEPA credit transfer?

Right now, I'm only interested in the most simple validation of the values returned by PaymentResponse (i.e., shipping address, credit card number, email, etc.). There is enough complexity just in that to keep us happy for a little while.

The above proprietary payment methods and associated tokens + related data will require that information be POSTed to some server for validation. The client can't do much else with that stuff.

Perhaps. We can all agree the payment data ultimately needs to be sent somewhere, but that somewhere is quite varied. It could be Braintree's client-side JavaScript API, where you don't submit() you client.tokenizeCard(), for example.

But at the end of the day, everything that leaves the browser conforms to the Fetch spec. So, irrespective of what any library does, it basically has only a few choices:

But these issues aside for the moment, I think the larger point is that the time for such a large change has passed. If this is a path the WG wants to go down, we should consider how we can add this later in a backwards compatible way. Would this work by allowing a user to do something like, paymentResponse.toFormData() or similar?

Yes, it would. Alternatively:

var data = new FormData(paymentResponse);

Few ways to skin this cat. However, I'm pretty sure this will come up sooner or later as more sites try to integrate this API with their existing payment systems.

marcoscaceres commented 7 years ago

So, it would be good to just add .formData() and .json() the PaymentResponse interface (and ditch the serializer). That would allow us to keep the current design, while addressing my concerns about this not being form data.

rsolomakhin commented 7 years ago

The serializer is a .toJSON() method, right? That's [automatically called](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#toJSON()_behavior) when developer writes JSON.stringify(response). The .json() method would not be automatically called by JSON.stringify(response). Can we stick with .toJSON() because of this?

marcoscaceres commented 7 years ago

Talked to @zkoch about this proposal. In light of Edge's announcement, I'm postponing this to "V2" pending feedback from the developer community. If no one complains about lack of integration with HTML form validation in 1 year, then we can probably close this.

marcoscaceres commented 7 years ago

I'm closing and will reopen it if need be in the future (based on developer feedback). There is nothing actionable here.