diff --git a/index.html b/index.html index 9013803b..a1a73052 100644 --- a/index.html +++ b/index.html @@ -79,7 +79,7 @@ }; -
+@@ -340,20 +345,44 @@
+ 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 }); ++
+ 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, + } ++
PaymentRequest
@@ -400,7 +453,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) { @@ -426,6 +482,80 @@doPaymentRequest();
+ 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.` }; + } + } ++
+ 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 }; + } ++
+ 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 @@ -540,14 +682,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:
sequence
<{{PaymentShippingOption}}>.
+ + 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. +
++ 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). +
++ A {{PaymentRequest}}'s {{PaymentRequest/onshippingaddresschange}} + attribute is an {{EventHandler}} for a {{PaymentRequestUpdateEvent}} + named shippingaddresschange. +
++ 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. +
++ A {{PaymentRequest}}'s {{PaymentRequest/onshippingoptionchange}} + attribute is an {{EventHandler}} for a {{PaymentRequestUpdateEvent}} + named shippingoptionchange. +
+@@ -1571,6 +1836,7 @@
dictionary PaymentDetailsBase { sequence<PaymentItem> displayItems; + sequence<PaymentShippingOption> shippingOptions; sequence<PaymentDetailsModifier> modifiers; };@@ -1587,6 +1853,41 @@
+ 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. +
+ +dictionary PaymentDetailsUpdate : PaymentDetailsBase { + DOMString error; PaymentItem total; + AddressErrors shippingAddressErrors; + PayerErrors payerErrors; object paymentMethodErrors; };@@ -1664,6 +1968,21 @@
+ enum PaymentShippingType { + "shipping", + "delivery", + "pickup" + }; ++
+ 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. +
++ The {{PaymentOptions/shippingType}} member only affects the user + interface for the payment request. +
++ 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. +
+@@ -1928,7 +2446,7 @@
+
The retry(|errorFields:PaymentValidationErrors|)
method
MUST act as follows:
dictionary PaymentValidationErrors { + PayerErrors payer; + AddressErrors shippingAddress; DOMString error; object paymentMethod; };
- The payment method identifier for the payment method - that the user selected to fulfill the transaction. -
-
- An {{object}} or dictionary generated by a payment
- method that a merchant can use to process or validate a
+
+ 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.
+
+ PayerErrors dictionary
+
+
+ dictionary PayerErrors {
+ DOMString email;
+ DOMString name;
+ DOMString phone;
+ };
+
+
+
+
+ const payer = {
+ email: "The domain is invalid.",
+ phone: "Unknown country code.",
+ name: "Not in database.",
+ };
+ await response.retry({ payer });
+
+
+ The payment method identifier for the payment method + that the user selected to fulfill the transaction. +
++ 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).
+ 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. +
++ 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. +
++ 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. +
++ 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. +
++ 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. +
++ Allows a developer to handle "payerdetailchange" events. +
++ 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=]. +
++ 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. +
+shippingaddresschange
+ shippingoptionchange
+ payerdetailchange
+ paymentmethodchange
@@ -2456,13 +3322,50 @@ + // ❌ 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:
@@ -2611,6 +3514,88 @@+ The shipping address changed algorithm runs when the user + provides a new shipping address. It MUST run the following steps: +
++ 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. +
++ The shipping option changed algorithm runs when the user + chooses a new shipping option. It MUST run the following steps: +
+id
string of the
+ {{PaymentShippingOption}} provided by the user.
+ + 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=]. +
+
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 @@ -2687,9 +3682,83 @@
+ 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: +
+The user accepts the payment request algorithm runs when the user accepts the payment request and confirms that they want @@ -2709,6 +3778,13 @@
sequence
<{{PaymentShippingOption}}>.
+ + 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. +
+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}} @@ -3212,8 +4430,23 @@
Where sharing of privacy-sensitive information might not be obvious to users (e.g., when [=payment handler/payment method changed @@ -3266,7 +4499,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.