Handling optional payment information with a service worker

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

Once a web-based payment app receives a payment request and initiates a payment transaction, 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
Handling optional payment information with a service worker

Inform the merchant of a 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
Credit Card Issuer 1 ****4242
Bank X ******123
B Credit Card Issuer 2 ****5678
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
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 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
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.

[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 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() returned will resolve with a 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.

Inform the merchant of a 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, 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
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 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() with the new address object.

Inform the merchant of a shipping address change
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);
      …

The merchant will receive a shippingaddresschange event from the Payment Request API so they can respond with the updated PaymentDetailsUpdate.

[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() returned will resolve with a 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.

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/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
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() with the new shipping option ID.

Inform the merchant of a shipping option change
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.

[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() returned will resolve with a 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.

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 object. The payment handler can use the result to reflect updated total price and shipping options to the UI.

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 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

Most of sample codes you saw in this document were excerpts from the following working sample app:

https://paymenthandler-demo.glitch.me

[payment handler] service worker

[payment handler] frontend

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.