How to adapt your web-based payment app to Web Payments and provide a better user experience for customers.


![Eiji Kitamura](https://web.dev/images/authors/agektmr.jpg) Eiji Kitamura [X](https://twitter.com/agektmr) [GitHub](https://github.com/agektmr) [Mastodon](https://infosec.exchange/@agektmr) [Bluesky](https://bsky.app/profile/agektmr.com)

<br />

> [!CAUTION]
> **Caution:** Shipping and address support in the [Payment Request API was once removed from the specification](https://github.com/w3c/payment-request/pull/955) and from Chrome. This change [was reverted in the spec](https://github.com/w3c/payment-request/pull/996). Thus, we've reverted the documentation about shipping and address as well.

Once [a web-based payment app receives a payment request and initiates a payment
transaction](https://web.dev/articles/orchestrating-payment-transactions), the service worker will act
as the hub for communication between the merchant and the payment app. This post
explains how a payment app can pass information about the payment method,
shipping address, or contact information to the merchant using a service worker.
![Handling optional payment information with a service worker](https://web.dev/static/articles/handling-optional-payment-information/image/handling-optional-payment-9e082afff222b.png) Handling optional payment information with a service worker

## Inform the merchant

It's important to inform the merchant of the following changes.

### Payment method change

Payment apps can support multiple payment instruments with different payment methods.

| Customer | Payment Method | Payment Instrument |
|---|---|---|
| A | Credit Card Issuer 1 | `****1234` |
| A | Credit Card Issuer 1 | `****4242` |
| A | Bank X | `******123` |
| B | Credit Card Issuer 2 | `****5678` |
| B | Bank X | `******456` |

For example, in the above table, Customer A's web-based wallet has two credit
cards and one bank account registered. In this case, the app is handling three
payment instruments (`****1234`, `****4242`, `******123`) and two payment
methods (Credit Card Issuer 1 and Bank X). On a payment transaction, the payment
app can let the customer pick one of the payment instruments and use it to pay
for the merchant.
![Payment method picker UI](https://web.dev/static/articles/handling-optional-payment-information/image/payment-method-picker-ui-83cf9bfe052f4.png) Payment method picker UI

The payment app can let the merchant know which payment method the customer
picked before sending the full payment response. This is useful when the
merchant wants to run a discount campaign for a specific payment method brand,
for example.

With the Web-based Payment Handler API, the payment app can send a "payment method change"
event to the merchant via a service worker to notify the new payment method
identifier. The service worker should invoke
`PaymentRequestEvent.changePaymentMethod()` with the new payment method
information.
![Inform the merchant of a payment method change](https://web.dev/static/articles/handling-optional-payment-information/image/inform-merchant-a-payme-9ae9bd3b7b774.png) Inform the merchant of a payment method change

Payment apps can pass a `methodDetails` object as the optional second argument
for `PaymentRequestEvent.changePaymentMethod()`. This object can contain
arbitrary payment method details required for the merchant to process the change
event.

> [!WARNING]
> **Warning:** Sharing detailed information about a customer with the merchant before the transaction is finalized is potentially a privacy risk. As a best practice, payment apps should omit unnecessary details as much as possible.

\[payment handler\] service-worker.js

    ...
    // Received a message from the frontend
    self.addEventListener('message', async e => {
      let details;
      try {
        switch (e.data.type) {
          ...
          case 'PAYMENT_METHOD_CHANGED':
            const newMethod = e.data.paymentMethod;
            const newDetails = e.data.methodDetails;
            // Redact or check that no sensitive information is passed in
            // `newDetails`.
            // Notify the merchant of the payment method change
            details =
              await payment_request_event.changePaymentMethod(newMethod, newDetails);
          ...

When the merchant receives a `paymentmethodchange` event from the Payment
Request API, they can update the payment details and respond with a
[`PaymentDetailsUpdate`](https://w3c.github.io/payment-request/#paymentdetailsupdate-dictionary)
object.

\[merchant\]

    request.addEventListener('paymentmethodchange', e => {
      if (e.methodName === 'another-pay') {
        // Apply $10 discount for example.
        const discount = {
          label: 'special discount',
          amount: {
            currency: 'USD',
            // The value being string complies the spec
            value: '-10.00'
          }
        };
        let total = 0;
        details.displayItems.push(discount);
        for (let item of details.displayItems) {
         total += parseFloat(item.amount.value);
        }
        // Convert the number back to string
        details.total.amount.value = total.toString();
      }
      // Pass a promise to `updateWith()` and send updated payment details
      e.updateWith(details);
    });

When the merchant responds, the promise that
[`PaymentRequestEvent.changePaymentMethod()`](https://w3c.github.io/payment-handler/#dom-paymentrequestevent-changepaymentmethod)
returned will resolve with a
[`PaymentRequestDetailsUpdate`](https://w3c.github.io/payment-handler/#the-paymentrequestdetailsupdate)
object.

\[payment handler\] service-worker.js

    ...
            // Notify the merchant of the payment method change
            details = await payment_request_event.changePaymentMethod(newMethod, newDetails);
            // Provided the new payment details,
            // send a message back to the frontend to update the UI
            postMessage('UPDATE_REQUEST', details);
            break;
    ...

Use the object to update the UI on the frontend. See [Reflect the updated
payment details](https://web.dev/articles/handling-optional-payment-information#reflect-the-updated-payment-details).

### Shipping address change

Payment apps can provide a customer's shipping address to the merchant as part
of a payment transaction.

This is useful for merchants because they can delegate the address collection to
payment apps. And, because the address data will be provided in [the standard
data format](https://w3c.github.io/payment-request/#dom-addressinit), the
merchant can expect to receive shipping addresses in consistent structure.

Additionally, customers can register their address information with their
preferred payment app and reuse it for different merchants.
![Shipping address picker UI](https://web.dev/static/articles/handling-optional-payment-information/image/shipping-address-picker-u-2f452a9725ce3.png) Shipping address picker UI

Payment apps can provide a UI to edit a shipping address or to select
pre-registered address information for the customer on a payment transaction.
When a shipping address is determined temporarily, the payment app can let the
merchant know of the redacted address information. This provides merchants with
multiple benefits:

- A merchant can determine whether the customer meets the regional restriction to ship the item (for example, domestic only).
- A merchant can change the list of shipping options based on the region of the shipping address (For example, international regular or express).
- A merchant can apply the new shipping cost based on the address and update the total price.

With the Web-based Payment Handler API, the payment app can send a "shipping address
change" event to the merchant from the service worker to notify the new shipping
address. The service worker should invoke
[`PaymentRequestEvent.changeShippingAddress()`](https://w3c.github.io/payment-handler/#dom-paymentrequestevent-changeshippingaddress)
with the [new address
object](https://www.w3.org/TR/payment-request/#dom-addressinit).
![Inform the merchant of a shipping address change](https://web.dev/static/articles/handling-optional-payment-information/image/inform-merchant-a-shipp-414e5048c9eb4.png) Inform the merchant of a shipping address change

\[payment handler\] service-worker.js

    ...
    // Received a message from the frontend
    self.addEventListener('message', async e => {
      let details;
      try {
        switch (e.data.type) {
          ...
          case 'SHIPPING_ADDRESS_CHANGED':
            const newAddress = e.data.shippingAddress;
            details =
              await payment_request_event.changeShippingAddress(newAddress);
          ...

Key-term: *Redacted address* . Informing the full shipping address to the merchant in this case is not necessary and risks customers' privacy. The merchant only receives the parts of the address that they need to determine the shipping cost. Specifically, the browser will clear the `organization`, `phone`, `recipient`, `addressLine` fields from the payment app provided address before raising the `shippingaddresschange` event in the merchant's DOM.

The merchant will receive a `shippingaddresschange` event from the Payment
Request API so they can respond with the updated [`PaymentDetailsUpdate`](https://w3c.github.io/payment-request/#paymentdetailsupdate-dictionary).

\[merchant\]

    request.addEventListener('shippingaddresschange', e => {
      // Read the updated shipping address and update the request.
      const addr = request.shippingAddress;
      const details = getPaymentDetailsFromShippingAddress(addr);
      // `updateWith()` sends back updated payment details
      e.updateWith(details);
    });

When the merchant responds, the promise
[`PaymentRequestEvent.changeShippingAddress()`](https://w3c.github.io/payment-handler/#dom-paymentrequestevent-changeshippingaddress)
returned will resolve with a
[`PaymentRequestDetailsUpdate`](https://w3c.github.io/payment-handler/#the-paymentrequestdetailsupdate)
object.

\[payment handler\] service-worker.js

    ...
            // Notify the merchant of the shipping address change
            details = await payment_request_event.changeShippingAddress(newAddress);
            // Provided the new payment details,
            // send a message back to the frontend to update the UI
            postMessage('UPDATE_REQUEST', details);
            break;
    ...

Use the object to update the UI on the frontend. See [Reflect the updated
payment details](https://web.dev/articles/handling-optional-payment-information#reflect-the-updated-payment-details).

## Inform the merchant of a shipping option change

Shipping options are delivery methods merchants use to ship purchased items to a customer. Typical shipping options include:

- Free shipping
- Express shipping
- International shipping
- Premium international shipping

Each comes with its own cost. Usually faster methods and options are more expensive.

Merchants using the Payment Request API can delegate this selection to a payment
app. The payment app can use the information to construct a UI and let the
customer pick a shipping option.
![Shipping option picker UI](https://web.dev/static/articles/handling-optional-payment-information/image/shipping-option-picker-ui-a337c6acfbf94.png) Shipping option picker UI

The list of shipping options specified in the merchant's Payment Request API is
propagated to the payment app's service worker as a property of
`PaymentRequestEvent`.

\[merchant\]

    const request = new PaymentRequest([{
      supportedMethods: 'https://bobbucks.dev/pay',
      data: { transactionId: '****' }
    }], {
      displayItems: [{
        label: 'Anvil L/S Crew Neck - Grey M x1',
        amount: { currency: 'USD', value: '22.15' }
      }],
      shippingOptions: [{
        id: 'standard',
        label: 'Standard',
        amount: { value: '0.00', currency: 'USD' },
        selected: true
      }, {
        id: 'express',
        label: 'Express',
        amount: { value: '5.00', currency: 'USD' }
      }],
      total: {
        label: 'Total due',
        amount: { currency: 'USD', value : '22.15' }
      }
    }, {  requestShipping: true });

The payment app can let the merchant know which shipping option the customer
picked. This is important for both the merchant and the customer because
changing the shipping option changes the total price as well. The merchant needs
to be informed of the latest price for the payment verification later and the
customer also needs to be aware of the change.

With the Payment Handler API, the payment app can send a "shipping option
change" event to the merchant from the service worker. The service worker should
invoke
[`PaymentRequestEvent.changeShippingOption()`](https://w3c.github.io/payment-handler/#dom-paymentrequestevent-changeshippingoption)
with the new shipping option ID.
![Inform the merchant of a shipping option change](https://web.dev/static/articles/handling-optional-payment-information/image/inform-merchant-a-shipp-8f67c21bfb63e.png) Inform the merchant of a shipping option change

\[payment handler\] service-worker.js

    ...
    // Received a message from the frontend
    self.addEventListener('message', async e => {
      let details;
      try {
        switch (e.data.type) {
          ...
          case 'SHIPPING_OPTION_CHANGED':
            const newOption = e.data.shippingOptionId;
            details =
              await payment_request_event.changeShippingOption(newOption);
          ...

The merchant will receive a `shippingoptionchange` event from the Payment
Request API. The merchant should use the information to update the total price
and then respond with the updated
[`PaymentDetailsUpdate`](https://w3c.github.io/payment-request/#paymentdetailsupdate-dictionary).

\[merchant\]

    request.addEventListener('shippingoptionchange', e => {
      // selected shipping option
      const shippingOption = request.shippingOption;
      const newTotal = {
        currency: 'USD',
        label: 'Total due',
        value: calculateNewTotal(shippingOption),
      };
      // `updateWith()` sends back updated payment details
      e.updateWith({ total: newTotal });
    });

When the merchant responds, the promise that
[`PaymentRequestEvent.changeShippingOption()`](https://w3c.github.io/payment-handler/#dom-paymentrequestevent-changeshippingoption)
returned will resolve with a
[`PaymentRequestDetailsUpdate`](https://w3c.github.io/payment-handler/#the-paymentrequestdetailsupdate)
object.

\[payment handler\] service-worker.js

    ...
            // Notify the merchant of the shipping option change
            details = await payment_request_event.changeShippingOption(newOption);
            // Provided the new payment details,
            // send a message back to the frontend to update the UI
            postMessage('UPDATE_REQUEST', details);
            break;
    ...

Use the object to update the UI on the frontend. See [Reflect the updated
payment details](https://web.dev/articles/handling-optional-payment-information#reflect-the-updated-payment-details).

## Reflect the updated payment details

Once the merchant finishes updating the payment details, the promises returned
from `.changePaymentMethod()`, `.changeShippingAddress()` and
`.changeShippingOption()` will resolve with a common
[`PaymentRequestDetailsUpdate`](https://w3c.github.io/payment-handler/#the-paymentrequestdetailsupdate)
object. The payment handler can use the result to reflect updated total price
and shipping options to the UI.

> [!NOTE]
> **Note:** The `PaymentRequestDetailsUpdate` object is passed from the merchant, and it contains the exact information the merchant put in the `PaymentDetailsUpdate` object with the Payment Request API. To ensure that merchants give you the information you expect, provide good developer documentation or provide a JavaScript library to control the information, or both.

Merchants may return errors for a few reasons:

- The payment method is not acceptable.
- The shipping address is outside of their supported regions.
- The shipping address contains invalid information.
- The shipping option is not selectable for the provided shipping address or some other reason.

Use the following properties to reflect the error status:

- **`error`**: Human readable error string. This is the best string to display to customers.
- **`shippingAddressErrors`** : [`AddressErrors`](https://www.w3.org/TR/payment-request/#dom-addresserrors) object that contains in-detail error string per address property. This is useful if you want to open a form that lets the customer edit their address and you need to point them directly to the invalid fields.
- **`paymentMethodErrors`**: Payment-method-specific error object. You can ask merchants to provide a structured error, but the Web Payments spec authors recommend keeping it a simple string.

## Sample code

> [!WARNING]
> **Warning:** This demo is no longer available, due to the Glitch shutdown.

Most of sample codes you saw in this document were excerpts from a sample application.

To try it out:

1. Go to <https://paymentrequest-demo.glitch.me/>.
2. Go to the bottom of the page.
3. Press **Add a payment button**.
4. Enter `https://paymenthandler-demo.glitch.me` to the **Payment Method Identifier** field.
5. Press **Pay** button next to the field.