From 0a5afe29c87a8b303148839d5a474dbef5e33697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20C=C3=A1ceres?= Date: Wed, 14 Aug 2024 23:37:48 +1000 Subject: [PATCH] Revert "Drop PaymentAddress, shipping + billing address support (#955)" (#996) * Revert "Drop PaymentAddress, shipping + billing address support (#955)" This reverts commit 486c07ae494ac64a31d7f90cf9df026b4250e878. * fixup link to create contact address --- index.html | 1319 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 1277 insertions(+), 42 deletions(-) diff --git a/index.html b/index.html index 9b708787..93fe6e2e 100644 --- a/index.html +++ b/index.html @@ -80,7 +80,7 @@ }; - +

Payment Request API

@@ -274,10 +274,15 @@

  • The |details|: The details of the transaction, as a PaymentDetailsInit dictionary. This includes total cost, and - optionally a list of goods or services being purchased. Additionally, - it can optionally include "modifiers" to how payments are made. For - example, "if you pay with a card belonging to network X, it incurs a - US$3.00 processing fee". + optionally a list of goods or services being purchased, for physical + goods, and shipping options. Additionally, it can optionally include + "modifiers" to how payments are made. For example, "if you pay with a + card belonging to network X, it incurs a US$3.00 processing fee". +
  • +
  • The |options|: Optionally, a list of things as {{PaymentOptions}} + that the site needs to deliver the good or service (e.g., for physical + goods, the merchant will typically need a physical address to ship to. + For digital goods, an email will usually suffice).
  • @@ -341,20 +346,44 @@

    label: "Value-Added Tax (VAT)", amount: { currency: "GBP", value: "5.00" }, }, - { - label: "Standard shipping", - amount: { currency: "GBP", value: "5.00" }, - }, ], total: { label: "Total due", // The total is GBP£65.00 here because we need to - // add tax and shipping. + // add shipping (below). The selected shipping + // costs GBP£5.00. amount: { currency: "GBP", value: "65.00" }, }, }; +
    +

    + Adding shipping options +

    +

    + Here we see an example of how to add two shipping options to the + |details|. +

    +
    +          const shippingOptions = [
    +            {
    +              id: "standard",
    +              // Shipping by truck, 2 days
    +              label: "🚛  Envío por camión (2 dias)",
    +              amount: { currency: "EUR", value: "5.00" },
    +              selected: true,
    +            },
    +            {
    +              id: "drone",
    +              // Drone shipping, 2 hours
    +              label: "🚀 Drone Express (2 horas)",
    +              amount: { currency: "EUR", value: "25.00" }
    +            },
    +          ];
    +          Object.assign(details, { shippingOptions });
    +        
    +

    Conditional modifications to payment request @@ -389,6 +418,30 @@

    Object.assign(details, { modifiers });

    +
    +

    + Requesting specific information from the end user +

    +

    + Some financial transactions require a user to provide specific + information in order for a merchant to fulfill a purchase (e.g., the + user's shipping address, in case a physical good needs to be + shipped). To request this information, a merchant can pass a third + optional argument (|options:PaymentOptions |) to the + {{PaymentRequest}} constructor indicating what information they + require. When the payment request is shown, the user agent will + request this information from the end user and return it to the + merchant when the user accepts the payment request. +

    +
    +          const options = {
    +            requestPayerEmail: false,
    +            requestPayerName: true,
    +            requestPayerPhone: false,
    +            requestShipping: true,
    +          }
    +        
    +

    Constructing a PaymentRequest @@ -401,7 +454,10 @@

               async function doPaymentRequest() {
                 try {
    -              const request = new PaymentRequest(methodData, details);
    +              const request = new PaymentRequest(methodData, details, options);
    +              // See below for a detailed example of handling these events
    +              request.onshippingaddresschange = ev => ev.updateWith(details);
    +              request.onshippingoptionchange = ev => ev.updateWith(details);
                   const response = await request.show();
                   await validateResponse(response);
                 } catch (err) {
    @@ -427,6 +483,80 @@ 

    doPaymentRequest();

    +
    +

    + Handling events and updating the payment request +

    +

    + Prior to the user accepting to make payment, the site is given an + opportunity to update the payment request in response to user input. + This can include, for example, providing additional shipping options + (or modifying their cost), removing items that cannot ship to a + particular address, etc. +

    +
    +          const request = new PaymentRequest(methodData, details, options);
    +          // Async update to details
    +          request.onshippingaddresschange = ev => {
    +            ev.updateWith(checkShipping(request));
    +          };
    +          // Sync update to the total
    +          request.onshippingoptionchange = ev => {
    +            // selected shipping option
    +            const { shippingOption } = request;
    +            const newTotal = {
    +              currency: "USD",
    +              label: "Total due",
    +              value: calculateNewTotal(shippingOption),
    +            };
    +            ev.updateWith({ total: newTotal });
    +          };
    +          async function checkShipping(request) {
    +            try {
    +              const { shippingAddress } = request;
    +
    +              await ensureCanShipTo(shippingAddress);
    +              const { shippingOptions, total } = await calculateShipping(shippingAddress);
    +
    +              return { shippingOptions, total };
    +            } catch (err) {
    +              // Shows error to user in the payment sheet.
    +              return { error: `Sorry! we can't ship to your address.` };
    +            }
    +          }
    +        
    +
    +
    +

    + Fine-grained error reporting +

    +

    + A developer can use the + {{PaymentDetailsUpdate/shippingAddressErrors}} member of the + {{PaymentDetailsUpdate}} dictionary to indicate that there are + validation errors with specific attributes of a {{ContactAddress}}. + The {{PaymentDetailsUpdate/shippingAddressErrors}} member is a + {{AddressErrors}} dictionary, whose members specifically demarcate + the fields of a [=physical address=] that are erroneous while also + providing helpful error messages to be displayed to the end user. +

    +
    +          request.onshippingaddresschange = ev => {
    +            ev.updateWith(validateAddress(request.shippingAddress));
    +          };
    +          function validateAddress(shippingAddress) {
    +            const error = "Can't ship to this address.";
    +            const shippingAddressErrors = {
    +              city: "FarmVille is not a real place.",
    +              postalCode: "Unknown postal code for your country.",
    +            };
    +            // Empty shippingOptions implies that we can't ship
    +            // to this address.
    +            const shippingOptions = [];
    +            return { error, shippingAddressErrors, shippingOptions };
    +          }
    +        
    +

    POSTing payment response back to a server @@ -493,7 +623,8 @@

    interface PaymentRequest : EventTarget { constructor( sequence<PaymentMethodData> methodData, - PaymentDetailsInit details + PaymentDetailsInit details, + optional PaymentOptions options = {} ); [NewObject] Promise<PaymentResponse> show(optional Promise<PaymentDetailsUpdate> detailsPromise); @@ -503,7 +634,12 @@

    Promise<boolean> canMakePayment(); readonly attribute DOMString id; + readonly attribute ContactAddress? shippingAddress; + readonly attribute DOMString? shippingOption; + readonly attribute PaymentShippingType? shippingType; + attribute EventHandler onshippingaddresschange; + attribute EventHandler onshippingoptionchange; attribute EventHandler onpaymentmethodchange; }; @@ -518,6 +654,12 @@

    while the user is providing input (up to the point of user approval or denial of the payment request).

    +

    + The {{PaymentRequest/shippingAddress}}, + {{PaymentRequest/shippingOption}}, and + {{PaymentRequest/shippingType}} attributes are populated during + processing if the {{PaymentOptions/requestShipping}} member is set. +

    A |request|'s payment-relevant browsing context is that @@ -541,14 +683,15 @@

    The {{PaymentRequest}} is constructed using the supplied sequence of PaymentMethodData |methodData| including any payment - method specific {{PaymentMethodData/data}}, and the - PaymentDetailsInit |details|. + method specific {{PaymentMethodData/data}}, the + PaymentDetailsInit |details|, and the {{PaymentOptions}} + |options|.

    The PaymentRequest(|methodData|, - |details|) constructor MUST act as follows: + |details|, |options|) constructor MUST act as follows:

    1. If [=this=]'s [=relevant global object=]'s [=associated @@ -669,6 +812,48 @@

    +
  • Let |selectedShippingOption| be null. +
  • +
  • If the {{PaymentOptions/requestShipping}} member of |options| is + present and set to true, process shipping options: +
      +
    1. Let |options:PaymentShippingOption| be an empty + sequence<{{PaymentShippingOption}}>. +
    2. +
    3. If the {{PaymentDetailsBase/shippingOptions}} member of + |details| is present, then: +
        +
      1. Let |seenIDs| be an empty set. +
      2. +
      3. For each |option:PaymentShippingOption| in + |details|.{{PaymentDetailsBase/shippingOptions}}: +
          +
        1. + Check and canonicalize amount + |item|.{{PaymentItem/amount}}. Rethrow any exceptions. +
        2. +
        3. If |seenIDs| contains + |option|.{{PaymentShippingOption/id}}, then throw a + {{TypeError}}. Optionally, inform the developer that + shipping option IDs must be unique. +
        4. +
        5. Otherwise, append + |option|.{{PaymentShippingOption/id}} to |seenIDs|. +
        6. +
        7. If |option|.{{PaymentShippingOption/selected}} is + true, then set |selectedShippingOption| to + |option|.{{PaymentShippingOption/id}}. +
        8. +
        +
      4. +
      +
    4. +
    5. Set |details|.{{PaymentDetailsBase/shippingOptions}} to + |options|. +
    6. +
    +
  • Let |serializedModifierData| be an empty list.
  • Process payment details modifiers: @@ -736,6 +921,8 @@

  • Set |request|.{{PaymentRequest/[[handler]]}} to `null`.
  • +
  • Set |request|.{{PaymentRequest/[[options]]}} to |options|. +
  • Set |request|.{{PaymentRequest/[[state]]}} to "[=PaymentRequest/created=]".
  • @@ -751,6 +938,17 @@

  • Set |request|.{{PaymentRequest/[[response]]}} to null.
  • +
  • Set the value of |request|'s {{PaymentRequest/shippingOption}} + attribute to |selectedShippingOption|. +
  • +
  • Set the value of the {{PaymentRequest/shippingAddress}} attribute + on |request| to null. +
  • +
  • If |options|.{{PaymentOptions/requestShipping}} is set to true, + then set the value of the {{PaymentRequest/shippingType}} attribute + on |request| to |options|.{{PaymentOptions/shippingType}}. Otherwise, + set it to null. +
  • Return |request|.
  • @@ -1209,6 +1407,64 @@

    make payment algorithm.

    +
    +

    + shippingAddress attribute +

    +

    + A {{PaymentRequest}}'s {{PaymentRequest/shippingAddress}} attribute + is populated when the user provides a shipping address. It is null by + default. When a user provides a shipping address, the shipping + address changed algorithm runs. +

    +
    +
    +

    + shippingType attribute +

    +

    + A {{PaymentRequest}}'s {{PaymentRequest/shippingType}} attribute is + the type of shipping used to fulfill the transaction. Its value is + either a {{PaymentShippingType}} enum value, or null if none is + provided by the developer during + [=PaymentRequest.PaymentRequest()|construction=] (see + {{PaymentOptions}}'s {{PaymentOptions/shippingType}} member). +

    +
    +
    +

    + onshippingaddresschange attribute +

    +

    + A {{PaymentRequest}}'s {{PaymentRequest/onshippingaddresschange}} + attribute is an {{EventHandler}} for a {{PaymentRequestUpdateEvent}} + named shippingaddresschange. +

    +
    +
    +

    + shippingOption attribute +

    +

    + A {{PaymentRequest}}'s {{PaymentRequest/shippingOption}} attribute is + populated when the user chooses a shipping option. It is null by + default. When a user chooses a shipping option, the shipping + option changed algorithm runs. +

    +
    +
    +

    + onshippingoptionchange attribute +

    +

    + A {{PaymentRequest}}'s {{PaymentRequest/onshippingoptionchange}} + attribute is an {{EventHandler}} for a {{PaymentRequestUpdateEvent}} + named shippingoptionchange. +

    +

    onpaymentmethodchange attribute @@ -1277,7 +1533,16 @@

    - [[\state]] + [[\options]] + + + The {{PaymentOptions}} supplied to the constructor. + + + + + [[\state]]

    @@ -1572,6 +1837,7 @@

             dictionary PaymentDetailsBase {
               sequence<PaymentItem> displayItems;
    +          sequence<PaymentShippingOption> shippingOptions;
               sequence<PaymentDetailsModifier> modifiers;
             };
             
    @@ -1588,6 +1854,41 @@

    {{PaymentDetailsInit/total}} amount is the sum of these items. +
    + shippingOptions member +
    +
    +

    + A sequence containing the different shipping options for the user + to choose from. +

    +

    + If an item in the sequence has the + {{PaymentShippingOption/selected}} member set to true, then this + is the shipping option that will be used by default and + {{PaymentRequest/shippingOption}} will be set to the + {{PaymentShippingOption/id}} of this option without running the + shipping option changed algorithm. If more than one item + in the sequence has {{PaymentShippingOption/selected}} set to + true, then the user agent selects the last one in the + sequence. +

    +

    + The {{PaymentDetailsBase/shippingOptions}} member is only used if + the {{PaymentRequest}} was constructed with {{PaymentOptions}} + and {{PaymentOptions/requestShipping}} set to true. +

    + +
    modifiers member
    @@ -1651,7 +1952,10 @@

               dictionary PaymentDetailsUpdate : PaymentDetailsBase {
    +            DOMString error;
                 PaymentItem total;
    +            AddressErrors shippingAddressErrors;
    +            PayerErrors payerErrors;
                 object paymentMethodErrors;
               };
             
    @@ -1665,6 +1969,21 @@

    {{PaymentDetailsUpdate}} dictionary:

    +
    + error member +
    +
    + A human-readable string that explains why goods cannot be shipped + to the chosen shipping address, or any other reason why no shipping + options are available. When the payment request is updated using + {{PaymentRequestUpdateEvent/updateWith()}}, the + {{PaymentDetailsUpdate}} can contain a message in the + {{PaymentDetailsUpdate/error}} member that will be displayed to the + user if the {{PaymentDetailsUpdate}} indicates that there are no + valid {{PaymentDetailsBase/shippingOptions}} (and the + {{PaymentRequest}} was constructed with the + {{PaymentOptions/requestShipping}} option set to true). +
    total member
    @@ -1677,6 +1996,19 @@

    is a negative number.

    +
    + shippingAddressErrors member +
    +
    + Represents validation errors with the shipping address that is + associated with the potential event target. +
    +
    + payerErrors member +
    +
    + Validation errors related to the payer details. +
    paymentMethodErrors member
    @@ -1753,6 +2085,133 @@

    +
    +

    + PaymentShippingType enum +

    +
    +        enum PaymentShippingType {
    +          "shipping",
    +          "delivery",
    +          "pickup"
    +        };
    +      
    +
    +
    + "shipping" +
    +
    + This is the default and refers to the [=physical address|address=] + being collected as the destination for [=shipping address|shipping=]. +
    +
    + "delivery" +
    +
    + This refers to the [=physical address|address=] being collected as + the destination for delivery. This is commonly faster than [=shipping + address|shipping=]. For example, it might be used for food delivery. +
    +
    + "pickup" +
    +
    + This refers to the [=physical address|address=] being collected as + part of a service pickup. For example, this could be the address for + laundry pickup. +
    +
    +
    +
    +

    + PaymentOptions dictionary +

    +
    +        dictionary PaymentOptions {
    +          boolean requestPayerName = false;
    +          boolean requestBillingAddress = false;
    +          boolean requestPayerEmail = false;
    +          boolean requestPayerPhone = false;
    +          boolean requestShipping = false;
    +          PaymentShippingType shippingType = "shipping";
    +        };
    +      
    +

    + The {{PaymentOptions}} dictionary is passed to the {{PaymentRequest}} + constructor and provides information about the options desired for the + payment request. +

    +
    +
    + requestBillingAddress member +
    +
    + A boolean that indicates whether the user agent SHOULD collect + and return the [=billing address=] associated with a payment + method (e.g., the billing address associated with a credit card). + Typically, the user agent will return the billing address as part of + the {{PaymentMethodChangeEvent}}'s + {{PaymentMethodChangeEvent/methodDetails}}. A merchant can use this + information to, for example, calculate tax in certain jurisdictions + and update the displayed total. See below for privacy considerations + regarding exposing user information. +
    +
    + requestPayerName member +
    +
    + A boolean that indicates whether the user agent SHOULD collect + and return the payer's name as part of the payment request. For + example, this would be set to true to allow a merchant to make a + booking in the payer's name. +
    +
    + requestPayerEmail member +
    +
    + A boolean that indicates whether the user agent SHOULD collect + and return the payer's email address as part of the payment request. + For example, this would be set to true to allow a merchant to email a + receipt. +
    +
    + requestPayerPhone member +
    +
    + A boolean that indicates whether the user agent SHOULD collect + and return the payer's phone number as part of the payment request. + For example, this would be set to true to allow a merchant to phone a + customer with a billing enquiry. +
    +
    + requestShipping member +
    +
    + A boolean that indicates whether the user agent SHOULD collect + and return a [=shipping address=] as part of the payment request. For + example, this would be set to true when physical goods need to be + shipped by the merchant to the user. This would be set to false for + the purchase of digital goods. +
    +
    + shippingType member +
    +
    + A {{PaymentShippingType}} enum value. Some transactions require an + [=physical address|address=] for delivery but the term "shipping" + isn't appropriate. For example, "pizza delivery" not "pizza shipping" + and "laundry pickup" not "laundry shipping". If + {{PaymentOptions/requestShipping}} is set to true, then the + {{PaymentOptions/shippingType}} member can influence the way the + user agent presents the user interface for gathering the + shipping address. +

    + The {{PaymentOptions/shippingType}} member only affects the user + interface for the payment request. +

    +
    +
    +

    PaymentItem dictionary @@ -1870,6 +2329,58 @@

    +
    +

    + PaymentShippingOption dictionary +

    +
    +        dictionary PaymentShippingOption {
    +          required DOMString id;
    +          required DOMString label;
    +          required PaymentCurrencyAmount amount;
    +          boolean selected = false;
    +        };
    +      
    +

    + The {{PaymentShippingOption}} dictionary has members describing a + shipping option. Developers can provide the user with one or more + shipping options by calling the + {{PaymentRequestUpdateEvent/updateWith()}} method in response to a + change event. +

    +
    +
    + id member +
    +
    + A string identifier used to reference this {{PaymentShippingOption}}. + It MUST be unique for a given {{PaymentRequest}}. +
    +
    + label member +
    +
    + A human-readable string description of the item. The user + agent SHOULD use this string to display the shipping option to + the user. +
    +
    + amount member +
    +
    + A {{PaymentCurrencyAmount}} containing the monetary amount for the + item. +
    +
    + selected member +
    +
    + A boolean. When true, it indicates that this is the default selected + {{PaymentShippingOption}} in a sequence. User agents SHOULD + display this option by default in the user interface. +
    +
    +

    PaymentResponse interface @@ -1882,6 +2393,11 @@

    readonly attribute DOMString requestId; readonly attribute DOMString methodName; readonly attribute object details; + readonly attribute ContactAddress? shippingAddress; + readonly attribute DOMString? shippingOption; + readonly attribute DOMString? payerName; + readonly attribute DOMString? payerEmail; + readonly attribute DOMString? payerPhone; [NewObject] Promise<undefined> complete( @@ -1890,6 +2406,8 @@

    ); [NewObject] Promise<undefined> retry(optional PaymentValidationErrors errorFields = {}); + + attribute EventHandler onpayerdetailchange; };

    @@ -1929,7 +2447,7 @@

    during {{PaymentResponse/retry()}}.

    -

    +

    The retry(|errorFields:PaymentValidationErrors|) method MUST act as follows:

    @@ -1965,6 +2483,35 @@

  • If |errorFields:PaymentValidationErrors| was passed:
      +
    1. Optionally, show a warning in the developer console if any of + the following are true: +
        +
      1. + |request|.{{PaymentRequest/[[options]]}}.{{PaymentOptions/requestPayerName}} + is false, and + |errorFields|.{{PaymentValidationErrors/payer}}.{{PayerErrors/name}} + is present. +
      2. +
      3. + |request|.{{PaymentRequest/[[options]]}}.{{PaymentOptions/requestPayerEmail}} + is false, and + |errorFields|.{{PaymentValidationErrors/payer}}.{{PayerErrors/email}} + is present. +
      4. +
      5. + |request|.{{PaymentRequest/[[options]]}}.{{PaymentOptions/requestPayerPhone}} + is false, and + |errorFields|.{{PaymentValidationErrors/payer}}.{{PayerErrors/phone}} + is present. +
      6. +
      7. + |request|.{{PaymentRequest/[[options]]}}.{{PaymentOptions/requestShipping}} + is false, and + |errorFields|.{{PaymentValidationErrors/shippingAddress}} is + present. +
      8. +
      +
    2. If @@ -2040,11 +2587,26 @@

                   dictionary PaymentValidationErrors {
      +              PayerErrors payer;
      +              AddressErrors shippingAddress;
                     DOMString error;
                     object paymentMethod;
                   };
                 
      +
      + payer member +
      +
      + Validation errors related to the payer details. +
      +
      + shippingAddress member +
      +
      + Represents validation errors with the {{PaymentResponse}}'s + {{PaymentResponse/shippingAddress}}. +
      error member
      @@ -2075,24 +2637,81 @@

  • - -
    -

    - methodName attribute -

    -

    - The payment method identifier for the payment method - that the user selected to fulfill the transaction. -

    -
    -
    -

    - details attribute -

    -

    - An {{object}} or dictionary generated by a payment - method that a merchant can use to process or validate a +

    +

    + PayerErrors dictionary +

    +
    +          dictionary PayerErrors {
    +            DOMString email;
    +            DOMString name;
    +            DOMString phone;
    +          };
    +          
    +

    + The {{PayerErrors}} is used to represent validation errors with one + or more payer details. +

    +

    + Payer details are any of the payer's name, payer's phone + number, and payer's email. +

    +
    +
    + email member +
    +
    + Denotes that the payer's email suffers from a validation error. + In the user agent's UI, this member corresponds to the input + field that provided the {{PaymentResponse}}'s + {{PaymentResponse/payerEmail}} attribute's value. +
    +
    + name member +
    +
    + Denotes that the payer's name suffers from a validation error. In + the user agent's UI, this member corresponds to the input field + that provided the {{PaymentResponse}}'s + {{PaymentResponse/payerName}} attribute's value. +
    +
    + phone member +
    +
    + Denotes that the payer's phone number suffers from a validation + error. In the user agent's UI, this member corresponds to the + input field that provided the {{PaymentResponse}}'s + {{PaymentResponse/payerPhone}} attribute's value. +
    +
    +
    +            const payer = {
    +              email: "The domain is invalid.",
    +              phone: "Unknown country code.",
    +              name: "Not in database.",
    +            };
    +            await response.retry({ payer });
    +          
    +
    +
    +
    +

    + methodName attribute +

    +

    + The payment method identifier for the payment method + that the user selected to fulfill the transaction. +

    +
    +
    +

    + details attribute +

    +

    + An {{object}} or dictionary generated by a payment + method that a merchant can use to process or validate a transaction (depending on the payment method).

    +
    +

    + shippingAddress attribute +

    +

    + If the {{PaymentOptions/requestShipping}} member was set to true in + the {{PaymentOptions}} passed to the {{PaymentRequest}} constructor, + then {{PaymentRequest/shippingAddress}} will be the full and final + [=shipping address=] chosen by the user. +

    +
    +
    +

    + shippingOption attribute +

    +

    + If the {{PaymentOptions/requestShipping}} member was set to true in + the {{PaymentOptions}} passed to the {{PaymentRequest}} constructor, + then {{PaymentRequest/shippingOption}} will be the + {{PaymentShippingOption/id}} attribute of the selected shipping + option. +

    +
    +
    +

    + payerName attribute +

    +

    + If the {{PaymentOptions/requestPayerName}} member was set to true in + the {{PaymentOptions}} passed to the {{PaymentRequest}} constructor, + then {{PaymentResponse/payerName}} will be the name provided by the + user. +

    +
    +
    +

    + payerEmail attribute +

    +

    + If the {{PaymentOptions/requestPayerEmail}} member was set to true in + the {{PaymentOptions}} passed to the {{PaymentRequest}} constructor, + then {{PaymentResponse/payerEmail}} will be the email address chosen + by the user. +

    +
    +
    +

    + payerPhone attribute +

    +

    + If the {{PaymentOptions/requestPayerPhone}} member was set to true in + the {{PaymentOptions}} passed to the {{PaymentRequest}} constructor, + then {{PaymentResponse/payerPhone}} will be the phone number chosen + by the user. +

    +

    requestId attribute @@ -2233,6 +2912,14 @@

    +
    +

    + onpayerdetailchange attribute +

    +

    + Allows a developer to handle "payerdetailchange" events. +

    +

    Internal Slots @@ -2283,6 +2970,141 @@

    +
    +

    + Shipping and billing addresses +

    +

    + The {{PaymentRequest}} interface allows a merchant to request from the + user [=physical address|physical addresses=] for the purposes of + shipping and/or billing. A shipping address and billing + address are [=physical address|physical addresses=]. +

    +
    +

    + AddressErrors dictionary +

    +
    +          dictionary AddressErrors {
    +            DOMString addressLine;
    +            DOMString city;
    +            DOMString country;
    +            DOMString dependentLocality;
    +            DOMString organization;
    +            DOMString phone;
    +            DOMString postalCode;
    +            DOMString recipient;
    +            DOMString region;
    +            DOMString sortingCode;
    +          };
    +        
    +

    + The members of the {{AddressErrors}} dictionary represent validation + errors with specific parts of a [=physical address=]. Each dictionary + member has a dual function: firstly, its presence denotes that a + particular part of an address is suffering from a validation error. + Secondly, the string value allows the developer to describe the + validation error (and possibly how the end user can fix the error). +

    +

    + Developers need to be aware that users might not have the ability to + fix certain parts of an address. As such, they need to be mindful not + to ask the user to fix things they might not have control over. +

    +
    +
    + addressLine member +
    +
    + Denotes that the [=physical address/address line=] has a validation + error. In the user agent's UI, this member corresponds to the input + field that provided the {{ContactAddress}}'s + {{ContactAddress/addressLine}} attribute's value. +
    +
    + city member +
    +
    + Denotes that the [=physical address/city=] has a validation error. + In the user agent's UI, this member corresponds to the input field + that provided the {{ContactAddress}}'s {{ContactAddress/city}} + attribute's value. +
    +
    + country member +
    +
    + Denotes that the [=physical address/country=] has a validation + error. In the user agent's UI, this member corresponds to the input + field that provided the {{ContactAddress}}'s + {{ContactAddress/country}} attribute's value. +
    +
    + dependentLocality member +
    +
    + Denotes that the [=physical address/dependent locality=] has a + validation error. In the user agent's UI, this member corresponds + to the input field that provided the {{ContactAddress}}'s + {{ContactAddress/dependentLocality}} attribute's value. +
    +
    + organization member +
    +
    + Denotes that the [=physical address/organization=] has a validation + error. In the user agent's UI, this member corresponds to the input + field that provided the {{ContactAddress}}'s + {{ContactAddress/organization}} attribute's value. +
    +
    + phone member +
    +
    + Denotes that the [=physical address/phone number=] has a validation + error. In the user agent's UI, this member corresponds to the input + field that provided the {{ContactAddress}}'s + {{ContactAddress/phone}} attribute's value. +
    +
    + postalCode member +
    +
    + Denotes that the [=physical address/postal code=] has a validation + error. In the user agent's UI, this member corresponds to the input + field that provided the {{ContactAddress}}'s + {{ContactAddress/postalCode}} attribute's value. +
    +
    + recipient member +
    +
    + Denotes that the [=physical address/recipient=] has a validation + error. In the user agent's UI, this member corresponds to the input + field that provided the {{ContactAddress}}'s + {{ContactAddress/addressLine}} attribute's value. +
    +
    + region member +
    +
    + Denotes that the [=physical address/region=] has a validation + error. In the user agent's UI, this member corresponds to the input + field that provided the {{ContactAddress}}'s + {{ContactAddress/region}} attribute's value. +
    +
    + sortingCode member +
    +
    + The [=physical address/sorting code=] has a validation error. In + the user agent's UI, this member corresponds to the input field + that provided the {{ContactAddress}}'s + {{ContactAddress/sortingCode}} attribute's value. +
    +
    +
    +

    Permissions Policy integration @@ -2326,6 +3148,49 @@

    Target + + + shippingaddresschange + + + {{PaymentRequestUpdateEvent}} + + + The user provides a new shipping address. + + + {{PaymentRequest}} + + + + + shippingoptionchange + + + {{PaymentRequestUpdateEvent}} + + + The user chooses a new shipping option. + + + {{PaymentRequest}} + + + + + payerdetailchange + + + {{PaymentRequestUpdateEvent}} + + + The user changes the payer name, the payer email, or the payer + phone (see payer detail changed algorithm). + + + {{PaymentResponse}} + + paymentmethodchange @@ -2457,13 +3322,50 @@

    changes made by the end user through the UI), developers need to immediately call {{PaymentRequestUpdateEvent/updateWith()}}.

    +
    +              // ❌ Bad - this won't work!
    +              request.onshippingaddresschange = async ev => {
    +                // await goes to next tick, and updateWith()
    +                // was not called.
    +                const details = await getNewDetails(oldDetails);
    +                // 💥 So it's now too late! updateWith()
    +                // throws "InvalidStateError".
    +                ev.updateWith(details);
    +              };
    +
    +              // ✅ Good - UI will wait.
    +              request.onshippingaddresschange = ev => {
    +                // Calling updateWith() with a promise is ok 👍
    +                const promiseForNewDetails = getNewDetails(oldDetails);
    +                ev.updateWith(promiseForNewDetails);
    +              };
    +            

    Additionally, {{PaymentRequestUpdateEvent/[[waitForUpdate]]}} prevents reuse of {{PaymentRequestUpdateEvent}}.

    +
    +              // ❌ Bad - calling updateWith() twice doesn't work!
    +              request.addEventListener("shippingaddresschange", ev => {
    +                ev.updateWith(details); // this is ok.
    +                // 💥 [[waitForUpdate]] is true, throws "InvalidStateError".
    +                ev.updateWith(otherDetails);
    +              });
    +
    +              // ❌ Bad - this won't work either!
    +              request.addEventListener("shippingaddresschange", async ev => {
    +                const p = Promise.resolve({ ...details });
    +                ev.updateWith(p);
    +                await p;
    +                // 💥 Only one call to updateWith() is allowed,
    +                // so the following throws "InvalidStateError"
    +                ev.updateWith({ ...newDetails });
    +              });
    +            

    + "PaymentRequestUpdateEvent/updatewith-method.https.html, PaymentRequestUpdateEvent/updateWith-incremental-update-manual.https.html"> The {{PaymentRequestUpdateEvent/updateWith()}} with |detailsPromise:Promise| method MUST act as follows:

    @@ -2612,6 +3514,88 @@

    +
    +

    + Shipping address changed algorithm +

    +

    + The shipping address changed algorithm runs when the user + provides a new shipping address. It MUST run the following steps: +

    +
      +
    1. Let |request:PaymentRequest| be the {{PaymentRequest}} object + that the user is interacting with. +
    2. +
    3. + Queue a task on the user interaction task source to + run the following steps: +
        +
      1. +
        +

        + The |redactList| limits the amount of personal information + about the recipient that the API shares with the merchant. +

        +

        + For merchants, the resulting {{ContactAddress}} object + provides enough information to, for example, calculate + shipping costs, but, in most cases, not enough information + to physically locate and uniquely identify the recipient. +

        +

        + Unfortunately, even with the |redactList|, recipient + anonymity cannot be assured. This is because in some + countries postal codes are so fine-grained that they can + uniquely identify a recipient. +

        +
        +
      2. +
      3. Let |redactList:list| be the empty list. Set |redactList| to + « "organization", "phone", "recipient", "addressLine" ». +
      4. +
      5. Let |address:ContactAddress| be the result of running the + steps to [=ContactsManager/create a contactaddress from + user-provided input=] with |redactList|. +
      6. +
      7. Set |request|.{{PaymentRequest/shippingAddress}} to + |address|. +
      8. +
      9. Run the PaymentRequest updated algorithm with + |request| and "shippingaddresschange". +
      10. +
      +
    4. +
    +
    +
    +

    + Shipping option changed algorithm +

    +

    + The shipping option changed algorithm runs when the user + chooses a new shipping option. It MUST run the following steps: +

    +
      +
    1. Let |request:PaymentRequest| be the {{PaymentRequest}} object + that the user is interacting with. +
    2. +
    3. + Queue a task on the user interaction task source to + run the following steps: +
        +
      1. Set the {{PaymentRequest/shippingOption}} attribute on + |request| to the id string of the + {{PaymentShippingOption}} provided by the user. +
      2. +
      3. Run the PaymentRequest updated algorithm with + |request| and "shippingoptionchange". +
      4. +
      +
    4. +
    +

    Payment method changed algorithm @@ -2625,6 +3609,16 @@

    method identifier of the payment handler the user is interacting with.

    +

    + When the user selects or changes a payment method (e.g., a credit + card), the {{PaymentMethodChangeEvent}} includes redacted billing + address information for the purpose of performing tax calculations. + Redacted attributes include, but are not limited to, [=physical + address/address line=], [=physical address/dependent locality=], + [=physical address/organization=], [=physical address/phone number=], + and [=physical address/recipient=]. +

    1. Let |request:PaymentRequest| be the {{PaymentRequest}} object that the user is interacting with. @@ -2655,7 +3649,7 @@

      PaymentRequest updated algorithm

      -

      +

      The PaymentRequest updated algorithm is run by other algorithms above to fire an event to indicate that a user has made a change to a {{PaymentRequest}} called |request| with an event @@ -2688,9 +3682,83 @@

    - User accepts the payment request algorithm + Payer detail changed algorithm

    + The user agent MUST run the payer detail changed algorithm + when the user changes the |payer name|, or the |payer email|, or the + |payer phone| in the user interface: +

    +
      +
    1. Let |request:PaymentRequest| be the {{PaymentRequest}} object + that the user is interacting with. +
    2. +
    3. If |request|.{{PaymentRequest/[[response]]}} is null, return. +
    4. +
    5. Let |response:PaymentResponse| be + |request|.{{PaymentRequest/[[response]]}}. +
    6. +
    7. + Queue a task on the user interaction task source to + run the following steps: +
        +
      1. Assert: |request|.{{PaymentRequest/[[updating]]}} is false. +
      2. +
      3. Assert: |request|.{{PaymentRequest/[[state]]}} is + "[=PaymentRequest/interactive=]". +
      4. +
      5. Let |options:PaymentOptions| be + |request|.{{PaymentRequest/[[options]]}}. +
      6. +
      7. If |payer name| changed and + |options|.{{PaymentOptions/requestPayerName}} is true: +
          +
        1. Set |response|.{{PaymentResponse/payerName}} attribute to + |payer name|. +
        2. +
        +
      8. +
      9. If |payer email| changed and + |options|.{{PaymentOptions/requestPayerEmail}} is true: +
          +
        1. Set |response|.{{PaymentResponse/payerEmail}} to |payer + email|. +
        2. +
        +
      10. +
      11. If |payer phone| changed and + |options|.{{PaymentOptions/requestPayerPhone}} is true: +
          +
        1. Set |response|.{{PaymentResponse/payerPhone}} to |payer + phone|. +
        2. +
        +
      12. +
      13. Let |event:PaymentRequestUpdateEvent| be the result of + creating an event using {{PaymentRequestUpdateEvent}}. +
      14. +
      15. Initialize |event|'s {{Event/type}} attribute to + "payerdetailchange". +
      16. +
      17. + Dispatch |event| at |response|. +
      18. +
      19. If |event|.{{PaymentRequestUpdateEvent/[[waitForUpdate]]}} is + true, disable any part of the user interface that could cause + another change to the payer details to be fired. +
      20. +
      21. Otherwise, set + |event|.{{PaymentRequestUpdateEvent/[[waitForUpdate]]}} to true. +
      22. +
      +
    8. +
    +
    +
    +

    + User accepts the payment request algorithm +

    +

    The user accepts the payment request algorithm runs when the user accepts the payment request and confirms that they want @@ -2710,6 +3778,13 @@

    take no further action. The user agent user interface SHOULD ensure that this never occurs. +
  • If the {{PaymentOptions/requestShipping}} value of + |request|.{{PaymentRequest/[[options]]}} is true, then if the + {{PaymentRequest/shippingAddress}} attribute of |request| is null or + if the {{PaymentRequest/shippingOption}} attribute of |request| is + null, then terminate this algorithm and take no further action. The + user agent SHOULD ensure that this never occurs. +
  • Let |isRetry:boolean| be true if |request|.{{PaymentRequest/[[response]]}} is not null, false otherwise. @@ -2744,6 +3819,48 @@

    to an object resulting from running the |handler|'s steps to respond to a payment request.

  • +
  • If the {{PaymentOptions/requestShipping}} value of + |request|.{{PaymentRequest/[[options]]}} is false, then set the + {{PaymentResponse/shippingAddress}} attribute value of |response| to + null. Otherwise: +
      +
    1. Let |shippingAddress:ContactAddress| be the result of + [=ContactsManager/create a contactaddress from user-provided + input=] +
    2. +
    3. Set the {{PaymentResponse/shippingAddress}} attribute value + of |response| to |shippingAddress|. +
    4. +
    5. Set the {{PaymentResponse/shippingAddress}} attribute value + of |request| to |shippingAddress|. +
    6. +
    +
  • +
  • If the {{PaymentOptions/requestShipping}} value of + |request|.{{PaymentRequest/[[options]]}} is true, then set the + {{PaymentResponse/shippingOption}} attribute of |response| to the + value of the {{PaymentResponse/shippingOption}} attribute of + |request|. Otherwise, set it to null. +
  • +
  • If the {{PaymentOptions/requestPayerName}} value of + |request|.{{PaymentRequest/[[options]]}} is true, then set the + {{PaymentResponse/payerName}} attribute of |response| to the payer's + name provided by the user, or to null if none was provided. + Otherwise, set it to null. +
  • +
  • If the {{PaymentOptions/requestPayerEmail}} value of + |request|.{{PaymentRequest/[[options]]}} is true, then set the + {{PaymentResponse/payerEmail}} attribute of |response| to the payer's + email address provided by the user, or to null if none was provided. + Otherwise, set it to null. +
  • +
  • If the {{PaymentOptions/requestPayerPhone}} value of + |request|.{{PaymentRequest/[[options]]}} is true, then set the + {{PaymentResponse/payerPhone}} attribute of |response| to the payer's + phone number provided by the user, or to null if none was provided. + When setting the {{PaymentResponse/payerPhone}} value, the user agent + SHOULD format the phone number to adhere to [[E.164]]. +
  • Set |request|.{{PaymentRequest/[[state]]}} to "[=PaymentRequest/closed=]".
  • @@ -2855,6 +3972,11 @@

  • Let |serializedModifierData| be an empty list.
  • +
  • Let |selectedShippingOption| be null. +
  • +
  • Let |shippingOptions| be an empty + sequence<{{PaymentShippingOption}}>. +
  • Validate and canonicalize the details:
    1. If the {{PaymentDetailsUpdate/total}} member of |details| @@ -2880,6 +4002,41 @@

  • +
  • If the {{PaymentDetailsBase/shippingOptions}} member of + |details| is present, and + |request|.{{PaymentRequest/[[options]]}}.{{PaymentOptions/requestShipping}} + is true, then: +
      +
    1. Let |seenIDs| be an empty set. +
    2. +
    3. For each |option| in + |details|.{{PaymentDetailsBase/shippingOptions}}: +
        +
      1. + Check and canonicalize amount + |option|.{{PaymentShippingOption/amount}}. If an + exception is thrown, then abort the update + with |request| and that exception. +
      2. +
      3. + If |seenIDs|[|option|.{{PaymentShippingOption/id}] + exists, then abort the update with |request| + and a {{TypeError}}. +
      4. +
      5. Append |option|.{{PaymentShippingOption/id}} to + |seenIDs|. +
      6. +
      7. Append |option| to |shippingOptions|. +
      8. +
      9. If |option|.{{PaymentShippingOption/selected}} is + true, then set |selectedShippingOption| to + |option|.{{PaymentShippingOption/id}}. +
      10. +
      +
    4. +
    +
  • If the {{PaymentDetailsBase/modifiers}} member of |details| is present, then:
      @@ -2891,7 +4048,9 @@

    1. For each {{PaymentDetailsModifier}} |modifier| in |modifiers|:
        -
      1. Run the steps to validate a payment method +
      2. + Run the steps to validate a payment method identifier with |modifier|.{{PaymentDetailsModifier/supportedMethods}}. If it returns false, then abort the update @@ -2980,6 +4139,21 @@

    2. +
    3. If the {{PaymentDetailsBase/shippingOptions}} member of + |details| is present, and + |request|.{{PaymentRequest/[[options]]}}.{{PaymentOptions/requestShipping}} + is true, then: +
        +
      1. Set + |request|.{{PaymentRequest/[[details]]}}.{{PaymentDetailsBase/shippingOptions}} + to |shippingOptions|. +
      2. +
      3. Set the value of |request|'s + {{PaymentRequest/shippingOption}} attribute to + |selectedShippingOption|. +
      4. +
      +
    4. If the {{PaymentDetailsBase/modifiers}} member of |details| is present, then:
        @@ -2993,6 +4167,50 @@

    5. +
    6. +

      + If + |request|.{{PaymentRequest/[[options]]}}.{{PaymentOptions/requestShipping}} + is true, and + |request|.{{PaymentRequest/[[details]]}}.{{PaymentDetailsBase/shippingOptions}} + is empty, then the developer has signified that there are + no valid shipping options for the currently-chosen + shipping address (given by |request|'s + {{PaymentRequest/shippingAddress}}). +

      +

      + In this case, the user agent SHOULD display an error + indicating this, and MAY indicate that the + currently-chosen shipping address is invalid in some way. + The user agent SHOULD use the + {{PaymentDetailsUpdate/error}} member of |details|, if it + is present, to give more information about why there are + no valid shipping options for that address. +

      +

      + Further, if + |details|["{{PaymentDetailsUpdate/shippingAddressErrors}}"] + member is present, the user agent SHOULD display an error + specifically for each erroneous field of the shipping + address. This is done by matching each present member of + the {{AddressErrors}} to a corresponding input field in + the shown user interface. +

      +

      + Similarly, if |details|["{{payerErrors}}"] member is + present and |request|.{{PaymentRequest/[[options]]}}'s + {{PaymentOptions/requestPayerName}}, + {{PaymentOptions/requestPayerEmail}}, or + {{PaymentOptions/requestPayerPhone}} is true, then + display an error specifically for each erroneous field. +

      +

      + Likewise, if + |details|.{{PaymentDetailsUpdate/paymentMethodErrors}} is + present, then display errors specifically for each + erroneous input field for the particular payment method. +

      +
  • @@ -3178,7 +4396,7 @@

    The user agent MUST NOT share information about the user with - a developer without user consent. + a developer (e.g., the [=shipping address=]) without user consent.

    In particular, the {{PaymentMethodData}}'s {{PaymentMethodData/data}} @@ -3213,8 +4431,23 @@

    data shared via the {{PaymentMethodChangeEvent}}'s {{PaymentMethodChangeEvent/methodDetails}} attribute. Requirements and approaches for minimizing shared data are likely to vary by - payment method. + payment method and might include:

    +
      +
    • Use of a "|redactList|" for physical addresses. The + current specification makes use of a "|redactList|" to redact the + [=physical address/address line=], [=physical address/organization=], + [=physical address/phone number=], and [=physical address/recipient=] + from a {{PaymentRequest/shippingAddress}}. +
    • +
    • Support for instructions from the payee identifying specific + elements to exclude or include from the payment method + response data (returned through {{PaymentResponse}}.|details|). The + payee might provide these instructions via + PaymentMethodData.|data|, enabling a payment method + definition to evolve without requiring changes to the current API. +
    • +

    Where sharing of privacy-sensitive information might not be obvious to users (e.g., when [=payment handler/payment method changed @@ -3267,7 +4500,9 @@

    For the user-facing aspects of Payment Request API, implementations integrate with platform accessibility APIs via form controls and other - input modalities. + input modalities. Furthermore, to increase the intelligibility of + total, shipping addresses, and contact information, implementations + format data according to system conventions.