Handling a payment request from the service worker side

Once your web-based payment app is registered, the service worker becomes the control point for the transaction. When the customer selects your app and the merchant calls PaymentRequest.show(), the service worker receives a paymentrequest event. This event object carries the details of that specific transaction.

// service-worker.js
self.addEventListener('paymentrequest', (event) => {
  // Preserve the event for later use
});

The PaymentRequestEvent exposes the transaction's key data, such as the requested payment details and the merchant's information. Keep a reference to this event, as you'll need it to respond once the payment flow completes.

Opening the payment handler window

Your service worker opens the payment app's user interface by calling PaymentRequestEvent.openWindow(). This window presents the customer with your app's interface for authentication, address selection, shipping options, and payment authorization.

You must provide a promise to PaymentRequestEvent.respondWith() that you resolve later with the final payment result. A common pattern is to use a PromiseResolver polyfill that lets you resolve that promise from anywhere, at any time.

// service-worker.js
let resolver;

self.addEventListener('paymentrequest', (event) => {
  event.respondWith(new Promise((resolve) => {
    resolver = resolve;
    event.openWindow('/payment-page');
  }));
});

Two-way communication with the frontend

The service worker exchanges messages with the payment handler window using ServiceWorkerController.postMessage(). To receive messages from the frontend, listen for message events on the service worker.

// service-worker.js
self.addEventListener('message', (event) => {
  // Handle message from the payment handler frontend
});

Waiting for the frontend ready signal

After the window opens, the frontend page broadcasts a ready state to the service worker. Your service worker can then relay transaction context to the webpage in that message response. This handshake prevents the frontend from rendering or acting before important transaction data is available.

// frontend (payment page)
navigator.serviceWorker.controller.postMessage({ type: 'ready' });
// service-worker.js
self.addEventListener('message', (event) => {
  if (event.data.type === 'ready') {
    // Send transaction details back to the client
    event.source.postMessage({
      type: 'transaction-details',
      total: paymentRequestEvent.paymentRequest.total
    });
  }
});

Passing transaction details

The service worker can send the transaction total and other needed data to the frontend at the appropriate moment. The amount of detail is up to your implementation's design.

Returning payment credentials

When the customer authorizes payment, the frontend notifies the service worker. The service worker then resolves the promise originally handed to PaymentRequestEvent.respondWith() with a PaymentHandlerResponse object. This completes the exchange with the merchant, carrying the customer's payment credentials.

// service-worker.js
self.addEventListener('message', (event) => {
  if (event.data.type === 'payment-approved') {
    resolver.resolve(event.data.response);
  }
});

Cancelling a transaction

To support customer cancellation, the frontend signals the service worker, which in turn resolves the response promise with null. Resolving with null tells the merchant's PaymentRequest that the transaction was cancelled.

// service-worker.js
self.addEventListener('message', (event) => {
  if (event.data.type === 'cancel') {
    resolver.resolve(null);
  }
});

The complete, runnable code for all of these flow steps is available in the open-source Web-based Payment Handler Demo on GitHub.

Beyond the basic flow

Runtime payment parameter changes — events where the merchant and payment handler exchange messages while the user is still on the window — extend this communication model. The same postMessage and event-listening pattern you've just set up is the foundation for that functionality. For details on implementing it, see Handling optional payment information with a service worker.