w3c / payment-request

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

Support for gift cards and discount codes #145

Open andrewpaliga opened 8 years ago

andrewpaliga commented 8 years ago

An important part of Shopify's checkout is the ability for the user to enter a discount code and or gift card at any point during the checkout (shown below). Once the discount or gift card code is verified the checkout balance is adjusted accordingly.

Step 1 - shipping

Step 2 - delivery

Step 3 - payment

The majority of checkout on the web support gift and promotional codes as well. Examples from Amazon and Nike below.

What is the strategy for accepting gift card and discount codes in the payments api? Will this be the responsibility of the user agent or the payment app?

There has been related discussion on the ability for the merchant to require the collection of certain shipping and billing information. This is similar but gift card and discount codes are more complex as they require an additional verification step to be carried out by the merchant. This verification could be accomplished through a callback to the merchant where they would be responsible for any updates.

Note: It’s more common to include gift card and discount entry in the payments step of checkout, however, we have experienced better cart conversation rates by making the option available throughout our checkout experience.

ianbjacobs commented 8 years ago

@andrewpaliga,

The current Working Group charter [1] puts loyalty and coupons out of scope:

"Recommendations for loyalty schemes and coupons, digital receipts, digital credentials, tickets, and location services are out of scope."

There are nascent discussions in the Interest Group [2] about the idea of a future version of user experience including application of loyalty and coupons. I am behind schedule but actioned to propose a plan and I'd welcome your help in that forum.

There are also more general discussions going on around verifiable claims (and there's a joint task force between the Credentials Community Group and the Web Payments Interest Group on that topic).

Ian

[2] https://www.w3.org/2016/03/07-wpay-minutes.html#item03

[1] https://www.w3.org/Payments/WG/charter-201510.html

andrewpaliga commented 8 years ago

@ianbjacobs thank you for the clarification. The slight confusion from my end was that the scope was listed under "Digital Wallets" while the interface for coupons could reside on the user agent, unless I'm missing something. We'd be happy to help define a process for applying gift card and discount codes.

halindrome commented 8 years ago

@andrewpaliga we are looking at coupons and loyalty cards in the context of ecommerce in the Web Payments Interest Group and also as a adjunct to "verifiable claims". C'mon in!

ianbjacobs commented 8 years ago

@andrewpaliga,

The slight confusion from my end was that the scope was listed under "Digital Wallets" while the interface for coupons could reside on the user agent, unless I'm missing something.

I agree that the UI could be on the client side, and coupons/loyalty feel like a reasonable extension to the payment request experience we are working on today.

I'd be happy to chat with you about this; I have chatted with @jnormore as well.

Ian

adrianhopebailie commented 8 years ago

What is the strategy for accepting gift card and discount codes in the payments api? Will this be the responsibility of the user agent or the payment app?

It seems to me that this would be easily supported by leveraging the user agent's data collection functionality as with shipping address etc.

This is further evidence that this functionality needs to be extensible.

Should we allow websites to request data that the user agent doesn't necessarily understand? Should we update my proposal on PR #65 to give the website more control over what is being requested?

WebIDL

enum DataType {
  "date",
  "number",
  "address",
  "email",
  "telephone",
  "url"
  "string",
  ...
};

dictionary DataDescriptor {
  required DOMString label;
  required DataType type;
  required boolean optional = false;
};

dictionary ShippingAddressDataDescriptor : DataDescriptor {
  sequence<ShippingOption> shippingOptions = false;
};

dictionary PaymentOptions {
  sequence<DataDescriptor> requestData;
  ...
};

Example website code:

{
  requestData: [
    {
      label: "Shipping Address",
      type: "address",
      shippingOptions: [...]
    }, 
    {
      label: "Email Address",
      type: "email",
      optional: true
    }, 
    {
      label: "Voucher Code",
      type: "string",
      optional: true
    }
  ] 
}
andrewpaliga commented 8 years ago

Should we allow websites to request data that the user agent doesn't necessarily understand?

This type of flexibility would certainly help us implement the spec as a replacement to our current checkout.

adrianhopebailie commented 8 years ago

Support for gift cards and discount codes is explicitly out of scope for this version of the API so I am going to mark this issue as postponed.

That said, there is a proposal here for a generic change to the API that would allow us to deal with this use case more explicitly down the line.

That proposal will need to be judged on it's own merit (as opposed to as a solution for this use case) and should be submitted as a PR for the group to discuss.

theball commented 8 years ago

I'd recommend that data that doesn't get stored and is useful across many sites not be added to the spec. It's valuable to store shipping addresses. That data will be used on many sites and saves a lot of typing. Coupon codes are used once, so the browser won't store it for future use. At that point it's just a substitute for a form field.

It becomes a slippery slope as the payment sheet then becomes a substitute for HTML screens and will cause fragmentation (i.e., "should I implement this field in HTML or the payment sheet?").

marcoscaceres commented 6 years ago

Weirdly, I thought I had already put together a proposal for this, but can't find it on Github.

Here is another one.

Proposal

Note: I wrote the following in such a way that we can just spin off a separate "Payment Request API: Coupons" spec. That way, this doesn't need to block on the main Payment Request API spec. It also makes it easier to shop around.

Additions to PaymentOptions dictionary

partial dictionary PaymentOptions {
 boolean requestCoupon;
}

requestCoupon member

Indicates that the merchant allows a coupon to be applied during request for payment.

For example:

const options = { 
  requestShipping: true,
  requestCoupon: true, // ✨
};
const request = new PaymentRequest(methods, details, options);

Additions to PaymentDetailsUpdate dictionary

partial dictionary PaymentDetailsUpdate {
  DOMString couponCode;
  boolean couponAccepted;
  CouponErrors couponErrors;
}

couponCode member

Serves as a placeholder coupon, which gets pre-populated in the payment sheet.

For example:

// fill it in, but don't apply it
request.show({ couponCode: "SPRING-SALE" });

// Show it, and apply it!
request.show({
  couponCode: "SPRING-SALE",
  couponAccepted: true,
});

couponAccepted member

Tells the browser to stop allowing the user to change the coupon code, and signal to the user that the code was accepted.

For example:

ev.updateWith({
   couponCode: "SPRING-SALE",
   couponAccepted: true,
});

couponErrors member

Allows the merchant to signal errors related to the coupon code.

For example:

ev.updateWith({
   couponErrors: { code: "Code already used." },
});

CouponErrors dictionary

Note: follows model from https://github.com/w3c/payment-request/issues/647#issuecomment-385852164

dictionary CouponErrors {
  DOMString code;
}

code member

The error corresponding to this particular code. See couponErrors member for example.

Additions to PaymentRequest interface

partial interface PaymentRequest {
  attribute EventHandler oncouponchange;
  readonly attribute DOMString? couponCode;
}

oncouponchange attribute

Fires when the user changes the coupon.

couponCode attribute

Allows merchants to access the updated coupon code during the request for payment.

Additions to PaymentResponse interface

partial interface PaymentResponse {
  readonly attribute DOMString? couponCode;
}

couponCode attribute

Allows merchants to access the updated coupon code in the response.

Example of usage

async function doPaymentRequest() {
  const options = { requestCoupon: true };
  const request = new PaymentRequest(methods, details, options);
  request.oncouponchange = ev => {
    const promise = applySweetSweetSavings(request, details);
    ev.updateWith(promise);
  };
  const response = await request.show();
  //...
  await response.complete("success");
  // post it somewhere, including the `couponCode` applied
  fetch("/invoices", {
    method: "POST",
    body: JSON.stringify(response.toJSON()),
  });
}

async function applySweetSweetSavings({ couponCode }, { displayItems }) {
  const couponErrors = await validateCouponCode(couponCode);
  if (couponErrors) {
    return { couponErrors };
  }
  // It's good, update total, add display item, whatever...
  const { newTotal, additionalDisplayItem } = applySweetSavings(couponCode);
  return {
    couponErrors: undefined,
    couponAccepted: true,
    total: newTotal,
    displayItems: [...displayItems, additionalDisplayItem].flatten(),
  };
}

Thoughts?

marcoscaceres commented 6 years ago

Fixed couple of little things above, added more examples, links, etc.

adrianhopebailie commented 6 years ago

Great place to start from! Nice work @marcoscaceres

Two minor concerns:

  1. The word coupon. I'm not sure it's universal terminology. Maybediscount code would be better?
  2. I think you have overloaded couponAccepted. The user should be allowed to change the code even if the merchant has accepted it.

Can't wait to get some sweet sweet savings!

mnoorenberghe commented 6 years ago

I think you have overloaded couponAccepted. The user should be allowed to change the code even if the merchant has accepted it.

I had the same thought. I don't think we would really need couponAccepted… if the merchant doesn't return an error from the event then we can assume it's valid. I also think we shouldn't restrict to a single applied coupon as that will still not be flexible enough for sites and we'd have to fix that later.

marcoscaceres commented 6 years ago

@adrianhopebailie wrote

  1. The word coupon. I'm not sure it's universal terminology. Maybe discount code would be better?

Agree that the name is not great and totally open to bikeshedding. The reason I didn't go with discountCode was that it might not always be a "discount".

Few options, from Wikipedia's coupon entry: "coupon codes", "promotional codes", "promotion codes", "discount codes", "keycodes", "promo codes", "surplus codes", "portable codes", "shopping codes", "voucher codes", "reward codes", "discount vouchers", "referral codes" or "source codes".

My personal choice would be: promoCode, as it's fairly generic, doesn't talk about discounts, and covers the "you get a free thing" use case... other option, offerCode.

@adrianhopebailie, wrote:

  1. The user should be allowed to change the code even if the merchant has accepted it.

Ah, can you help me understand this a bit more? do you mean remove it? Like, this user story:

  1. "Let's see what this gives me!", user enters code "MYSTERY-SAVINGS".
  2. "Oh! I get 10% off everything!... maybe I should save this for next time I do a big purchase".
  3. User removes code somehow.
  4. User hits "Pay".

Or, this user story:

  1. "I have 2 codes, but I can't remember which is better. Gonna try the first...".
  2. User enters code.
  3. Merchant responds with "10% off!".
  4. User removes first code, User enters code 2.
  5. Merchant responds with "You get a free Widget!".
  6. User decides they prefer 10% off, so renters code 1.
  7. User hits "Pay".

So, with the above, the payment sheet should have the following affordances:

  1. enter promo code.
  2. edit a promo code - even when not in error.
  3. remove a promo code.
  4. display error - allowing the code to be fixed or removed.

@mnoorenberghe wrote:

I don't think we would really need couponAccepted… if the merchant doesn't return an error from the event then we can assume it's valid.

Agree! Will drop couponAccepted.

I also think we shouldn't restrict to a single applied coupon as that will still not be flexible enough for sites and we'd have to fix that later.

Yes, I was thinking the same thing. My limited personal experience was that I'd only ever seen the ability to apply a single code. Folks who know better: @andrewpaliga, @michelle-stripe, @jenan-stripe, your feedback is critical here:

ianbjacobs commented 6 years ago

+1 to offerCode. I can also live with promoCode.

mnoorenberghe commented 6 years ago

Do you have stores that accept more than one coupon code?

I'm pretty sure I've used multiple coupon codes on a site before. e.g. coupon code A is for buying N products by Brand 1, coupon code B is a discount offered solely by the merchant, not in partnership with a brand.

If yes, does the number of coupon codes allowed need to be limited by the merchant?

If so, that should be handled by the event + updateWith rather than be passed on to the UAs to handle IMO.

Could someone remind me of the recent discussion that sparked re-opening this? I believe it was from a merchant test IIRC. Did the test allow for coupon code entry before the PaymentRequest dialog appears?

Krystosterone commented 6 years ago

From what I gather, it might have been sparked because of a recent call where Andre (https://github.com/lyverovski) mentioned some experiments we've been running around Payment Request. See minutes here: https://www.w3.org/2018/05/03-wpwg-minutes

lyver: We've been running some experiments for several months with PR API running on a number of enterprise level clients ... we are putting together a brief that we will share with this group ... there are two points that I want to raise: merchants are not necessarily skittish; they have trusted us as a platform ... but what's missing for them is support for discount codes. Up to half of people have dropped out since they want to go through legacy UX to get the discount code. I think that is missing from the focus of the next six months ... the other thing is that buyers themselves are skittish...they see the payment sheet open and has little merchant branding, which may scare some off.

If this is truly what re-opened the discussion and what you were asking about; our experiment allowed the customer to click a "Discount or gift card?". That would bring them to our normal checkout rather then them being able to apply a coupon code and then proceeding with Payment Request.

As Andre mentioned, we'll be sharing the rest of the learnings and the experiment setup soon.

marcoscaceres commented 6 years ago

If so, that should be handled by the event + updateWith rather than be passed on to the UAs to handle IMO.

So, just error the last one? Could work, but it means the user might enter more codes than they need to. If we restrict it, to say: "3", then the browser just doesn't provide an option to add another.

Could someone remind me of the recent discussion that sparked re-opening this?

See priorities (and related "ACTION"): https://www.w3.org/2018/05/03-wpwg-minutes#item03

Taking feedback thus far...

Proposal - multiple offer codes

This proposal enhances the Payment Request API, allowing an end user to enter one or more "offer codes" when making a purchase.

Offer codes, known also as coupon codes, promotional codes, discount codes, etc. are tokens issued by merchants that, when applied to a payment request by an end user, can affect a payment request by, for example, providing a discount.

Via the API, merchants can indicate if they support offer codes at all, and limit the number of offer codes the user can provide, as well as inform the end user of potential errors with the provided offer code(s).

Out of scope

The following are all out of scope, and are the responsibility of merchants:

Model

An offer code is a string that represents an opaque token.

An offer code can only be apply once per payment request, allowing them to be uniquely identified.

For example, "SUMMER-SALE".

Additions to PaymentOptions dictionary

partial dictionary PaymentOptions {
  // 255 max, 0 means no offer codes accepted
  octet requestOfferCodes = 0;
}

requestOfferCodes member

Indicates that the merchant allows the user to apply N offer codes during request for payment.

Value of 0 means that the merchant does not accept any offer codes.

For example:

const options = {
  requestShipping: true,
  requestOfferCodes: 3, // Up to 3 ✨
};
const request = new PaymentRequest(methods, details, options);

Additions to PaymentDetailsUpdate dictionary

partial dictionary PaymentDetailsUpdate {
  sequence<DOMString> offerCodes;
  sequence<OfferCodeError> offerCodeErrors;
}

offerCodes member

A list of strings representing offer codes, which are presented to the user.

For example:

// Show it
request.show({
  offerCodes: ["SPRING-SALE", "BONUS-DISCOUNT"],
  displayItems: itemsIncludingDiscounts,
});

OfferCodeErrors member

Allows the merchant to signal errors related to one or more offer codes.

In case of duplicates, last one wins.

For example:

ev.updateWith({
  offerCodeErrors: [
    { offerCode: "SPRING-SALE", error: "Code already used." },
    // Duplicate, oops
    { offerCode: "SUMMER-SALE", error: "This is ignored." },
    { offerCode: "SUMMER-SALE", error: "Invalid code! Try again." },
  ],
});

OfferCodeError dictionary

dictionary OfferCodeError {
  required DOMString offerCode;
  DOMString error;
}

offerCode member

The identifier of the offending offer code.

Additions to PaymentRequest interface

partial interface PaymentRequest {
  attribute EventHandler onoffercodeschange;
  readonly attribute FrozenArray<DOMString> offerCodes;
}

onoffercodeschange attribute

Fires when the user adds, deletes, or updates an offer code.

offerCodes attribute

Allows merchants to access the updated offer codes during the request for payment.

Additions to PaymentResponse interface

partial interface PaymentResponse {
  readonly attribute FrozenArray<DOMString> offerCodes;
}

offerCodes attribute

The final list of offer codes that were applied to the payment request.

Example of usage

async function doPaymentRequest() {
  const options = { requestOfferCodes: 5 };
  const request = new PaymentRequest(methods, details, options);
  request.onoffercodeschange = ev => {
    const promise = applySweetSweetSavings(request, details);
    ev.updateWith(promise);
  };
  const response = await request.show();
  //...
  await response.complete("success");
  // post it somewhere, including the `offerCode` applied
  fetch("/invoices", {
    method: "POST",
    body: JSON.stringify(response.toJSON()),
  });
}

async function validateOfferCode(code) {
   return fetch(`is-valid?code=${code}`).then(r => r.json());
}

async function applySweetSweetSavings({ offerCodes }, { displayItems }) {
  const offerCodeErrors = await Promise.all(
    offerCodes.map(validateOfferCode)
  ).then(arr => arr.flatten());
  if (offerCodeErrors.length) {
    return { offerCodeErrors };
  }
  // It's good, update total, add display item, whatever...
  const { newTotal, additionalDisplayItem } = applySweetSavings(offerCode);
  return {
    OfferCodeError: undefined,
    total: newTotal,
    displayItems: [...displayItems, additionalDisplayItem].flatten(),
  };
}
Krystosterone commented 6 years ago

:wave: I can provide some guidance from the Shopify side here. Take all of this with a grain of salt as what is best for our platform might not apply for the broader public of Payment Request.

To start it off, Shopify uses the field for both discount codes and gift cards alike. Now the interesting part here is that we only ever allow the customer to apply one of each. I'll follow below with some user stories and scenarios that we found to work best for the user experience.

As a side note (and this might become more of a browser vendor implementation detail), we change the copy on the offer code field per merchant use case. For example:

We found this to be the least confusing of experiences for the user. This might lead us to wonder if the copy should be customizable by the integrator. 🤷‍♂️

Finally, we allow discount codes to be applied through URL permalinks, to support email promotions for example.

As such, here are the features that could be interesting to have (user stories and scenarios to follow):

Notice that the feature of editing a offer code is not mentioned; for Shopify at least, we find that as long as a offer code can be overriden (the last offer code applied - be it a discount or gift card - takes precendence as long as it's valid) and these can be removed, there would be no need to have the ability to edit one. Thus, we also never deactivate or change the state of the offer code field; rather, offer code line iterms surface as removable receipt line items on our checkout.

All of that being said, I love your latest proposition @marcoscaceres! I'd love to discuss a bit more the following things:

  1. Is the requestOfferCodes attribute necessary? If we would consider Payment Request having a single offer code input field that is either there or not, with no possibility to edit offer codes (only to override them), is that attribute still necessary? I quite liked the idea of keeping it just as a boolean (as per the first draft)
  2. How does offerCodes being populated affect the UI? i.e. Does the integrator still have to add a display item themselves for an offer code line item?

This is to say, should the offer code input concept be seperated from the offer code line item? Could it be possible to make offerCodes part of the displayItems rather than a seperate entity?

Thanks y'all on your comprehension for this very long post. Hopefully this provides more insight than noise on further offer code discussions.

User stories

As a merchant
I want to offer discount codes on my store
In order to drive sales to my shop

As a merchant
I want to offer gift cards on my store
In order to drive recurring sales to my shop

Scenario: A customer applies multiple reductions to their checkout
  Given a merchant supports both discount codes and gift cards
  And the merchant has a "PROMO10" discount code, which reduces the order by 10%
  And the merchant has a "PROMO20" discount code, which reduces the order by 20%
  And the merchant has a "THANKS" gift card code, which reduces the order by 10$
  When I start the checkout process
  And I have a single "Digital Game" item of value 20$
  Then I see a field with copy "Discount code or Gift Cards"

  When I enter the discount code "PROMO10"
  Then I see my receipt line items as such:
    | line item    | value |
    | Digital Game | 20$   |
    | PROMO10      | -10%  |
    | Total        | 18$   |

  When I enter a discount code "PROMO20"
  Then I see my receipt line items as such:
    | line item    | value |
    | Digital Game | 20$   |
    | PROMO10      | -20%  |
    | Total        | 16$   |

  When I enter a gift card code "THANKS"
  Then I see my receipt line items as such:
    | line item    | value |
    | Digital Game | 20$   |
    | PROMO20      | -20%  |
    | THANKS       | 10$   |
    | Total        | 6$    |

  When I remove the discount code
  Then I see my receipt line items as such:
    | line item    | value |
    | Digital Game | 20$   |
    | THANKS       | 10$   |
    | Total        | 10$   |
marcoscaceres commented 6 years ago

@Krystosterone, this is invaluable feedback! Thank you!

To start it off, Shopify uses the field for both discount codes and gift cards alike. Now the interesting part here is that we only ever allow the customer to apply one of each. I'll follow below with some user stories and scenarios that we found to work best for the user experience.

I wonder if we need to treat each differently, because each can have a different input modality. For instance, I can scan iTunes gift cards with my camera.

screenshot 2018-05-08 18 14 59

(We don't need to get this fancy... just showing what user's expectations might be - at least, I'd want a comparable experience, because the iTunes OCR input is a real treat on many levels.)

As a side note (and this might become more of a browser vendor implementation detail), we change the copy on the offer code field per merchant use case. For example:

Then we could have:

requestOfferCodes: true,
requestGiftCards: false,

That would also allow the browser to localize the copy in the way you demonstrated.

Finally, we allow discount codes to be applied through URL permalinks, to support email promotions for example.

I don't know what this means exactly. That the code is a URL? That's ok, I think.

rather, offer code line items surface as removable receipt line items on our checkout.

Ah, so PaymentItem have an associated offerCode, and then we would need some kind of event for the removal (current proposed model might allow for this).

It might get tricky if you have multiple displayItems items with the same code applied. I guess the browser groups them together by code, and then allows trashing them all at once... yeah, that might be nice.

Is the requestOfferCodes attribute necessary? If we would consider Payment Request having a single offer code input field that is either there or not, with no possibility to edit offer codes (only to override them), is that attribute still necessary? I quite liked the idea of keeping it just as a boolean (as per the first draft)

In the case where you can trash offer codes from the displayItem, directly, then it could work.

How does offerCodes being populated affect the UI? i.e. Does the integrator still have to add a display item themselves for an offer code line item?

My thinking was that they would show up in the UI, but the user would need to apply the ones they want (if any).

This is to say, should the offer code input concept be seperated from the offer code line item? Could it be possible to make offerCodes part of the displayItems rather than a seperate entity?

Yeah, something like that could work. I'll need to rethink it all now :)

adrianhopebailie commented 6 years ago

@marcoscaceres Bike-shedding yay! I propose: checkoutCode 😬

+1 to both stories. I agree we can just drop the couponAccepted flag. "Applying" the coupon is done by the merchant through the line items. i.e. If the merchant accepts a coupon they can:

  1. Return empty/undefined couponErrors
  2. Add a line item to reflect the discount indicating to the user that it was accepted
domenic commented 6 years ago

I am a little skeptical that we really need to encompass every payment flow, like coupon codes, in the browser's built-in payment sheet. This seems like something that can easily be applied in the "shopping cart" phase, before clicking the "checkout" button. But I guess I'll leave questions of "should we" to the experts/working group, and instead help with the "how do we" questions...

partial dictionary PaymentOptions {
 // 255 max, 0 means no offer codes accepted
 octet requestOfferCodes = 0;
}

Don't use octet in this way, or impose arbitrary computer-ese limits like 255 in scenarios where they aren't domain-appropriate. Note that in this design passing 257 will be the same as passing 1.

See https://w3ctag.github.io/design-principles/#numeric-types for the general guidance in this area.

ev.updateWith({
  offerCodeErrors: [
    { offerCode: "SPRING-SALE", error: "Code already used." },
    // Duplicate, oops
    { offerCode: "SUMMER-SALE", error: "This is ignored." },
    { offerCode: "SUMMER-SALE", error: "Invalid code! Try again." },
  ],
});

To avoid the duplicate problem, you could use record<DOMString, DOMString>, so that the above becomes

ev.updateWith({
  offerCodeErrors: {
    "SPRING-SALE": "Code already used.",
    "SUMMER-SALE": "Invalid code! Try again."
  }
});

Maybe this is inconsistent with other error-providing plans though.

n3o77 commented 6 years ago

I'm also not sure if it makes a lot of sense that coupons can be applied in the browser payment sheet. What would happen if the customer uses a coupon / gift cards which will reduce the payable amount to zero. As @theball already said, I am also seeing the payment sheet more for data, which rarely changes from a customer perspective. Coupon codes, gift cards or other promotions are often handled separately in many shops which could also lead to implementation problems.

stpeter commented 6 years ago

@domenic said:

I am a little skeptical that we really need to encompass every payment flow, like coupon codes, in the browser's built-in payment sheet. This seems like something that can easily be applied in the "shopping cart" phase, before clicking the "checkout" button.

It does seem to introduce a fair amount of complexity. If we supported offer codes only in the shopping cart phase, presumably we could show the amount applied in a "view order details" interface within the payment sheet (as a PaymentItem with a negative amount)...

Krystosterone commented 6 years ago

I think it's very valuable to ask ourselves if coupon codes should be in scope of Payment Request. Hopefully the discussions we have here might provide future heuristics on deciding if further iterations should benefit from certain features or not.

Coupon codes seem to be one of the last concepts that have impact on receipt line items and total price, but are still not part of the Payment Request. The argument of presenting the field before the sheet is valid, but we would argue that this is not be the best of user experiences. For example, using Baymard's E-Commerce Checkout Usability Benchmark and Report[1], they mention:

On sites that neglected to do neither, i.e. both displayed coupon and promotional form fields directly in the default form flow, and placed them above the primary purpose of the checkout step, severe issues were observed, with 30-60+% of subjects coming to a full stop and examining the promotions. For some it even caused site abandonments.

As such, they suggest:

  1. Hiding the promo code field behind a clickable link
  2. Placing the promo code link after the payment fields

We suggest that not having promo codes in the sheet puts the onus on the merchant to figure out what is the best way to allow for promo codes to be accepted on their store (through the cart, for example). Depending on the merchant offerings, this might become quite intricate to do correctly and could very well lead to customers never clicking on the "checkout" button in the first place.

These two best practices can very well be implemented by browser vendors and provide for the best user experience. At Shopify, we follow the same rationale, as our very own experimentation also echoed the same issues.

To provide context, Shopify allows merchants to have full control over their storefront, but not over the checkout (this last part is controlled by our platform). The reasoning being that, as a platform, we want to provide the best UI and UX for the purchase experience to the customer. This also meant that when implementing promo codes, we only implemented them on the checkout process, rather than on the cart. We could have very well implemented an API for merchants to present their own promo code field, but went against that approach for all of the reasons stated previously.

As for the complexity, I would argue that it might be valuable to first decide if this is a problem worth solving. We can then iterate on the optimal API possible and discuss complexity as we go.

[1] https://baymard.com/checkout-usability

marcoscaceres commented 6 years ago

As for the complexity, I would argue that it might be valuable to first decide if this is a problem worth solving.

Agree, and it's clear this is a larger/more complicated than we (ok, just me! 😓) had initially envisioned.

We can then iterate on the optimal API possible and discuss complexity as we go.

I'd like for us to finish the things in the CR-Exit list, then come back to this when we have more available resources to fully explore the problem and prototype solutions. Nevertheless, I'd encourage interested folks to continue the discussion.

adrianhopebailie commented 6 years ago

I don't want to be THAT GUY but I think we're starting to feel the pain from ignoring the early calls within the WG to design a basic payment API and a separate "checkout API" that was a composition of payments and other data collection.

By incorporating the collection of non-payment data like shipping address, email address etc. into the API in the way we did we must now constantly evaluate new requests for data collection against an unclear set of criteria for inclusion.

I agree with @marcoscaceres that we should focus, right now, on the CR-Exit list of issues and perhaps consider a quite different shape for a v2 API that is more composable?

Krystosterone commented 6 years ago

@marcoscaceres @adrianhopebailie Totally didn't want to take up your time, as CR-Exit issues are more pressing for sure!

Whenever it's appropriate, we can circle back to this. If I ever think of anything else, I'll just keep posting here but feel free to ignore me for the time being.

v2 API that is more composable

Would love to see where that would lead!

ianbjacobs commented 6 years ago

@adrianhopebailie mentioned and "unclear set of criteria for inclusion" of features like discount codes.

I get the sense from discussions that lots of users may be dropping out of the sheet since they can't enter a discount code. That motivates me to want to hear good proposals for inclusion because:

So while the criteria for inclusion may not be clear, this particular feature seems to be important to user acceptance of the sheet.

Ian

stpeter commented 6 years ago

Agreed, especially if by "user" we mean "merchant". ;-)

adrianhopebailie commented 6 years ago

Agreed, especially if by "user" we mean "merchant".

Actually I think we mean "shoppers". i.e. Shoppers are confronted with a checkout sheet and have no way to enter a discount code so they drop out. This is obviously bad and, I agree, sufficient motivation to figure this out.

There are two issues here:

  1. Specific to discount codes
  2. A broader architectural question about how to improve the API so we don't have this same conversation every 6 months when some new data suggests "LOTS of users are dropping out" because of another data field we need to capture.

Let's constrain this thread to 1 and anyone with thoughts on 2 can make some proposals that we might consider as a WG for a v2 (@Krystosterone - tell us what your ideal API would look like, knowing now what we do about how this all fits together)

stpeter commented 6 years ago

@adrianhopebailie Actually I think we mean merchants, because when @lyverovski gave us a report on their deployment experiment recently the feedback was "merchants are dropping out because they can't show discount codes to shoppers". If merchants can't deploy discount codes, shoppers can't use them, so perhaps we're splitting hairs. :-)

ianbjacobs commented 6 years ago

Hi all, we discussed at the WPWG teleconference today: https://www.w3.org/2018/05/31-wpwg-minutes#item01

The sentiment on the call was:

Ian