v149 · Web APIs · Handler Integration

Handler Integration

Complete code walkthrough: the payment handler service worker rejecting with a structured error, and the merchant page catching and acting on it. Before/after comparison for the Chrome 149 change.

before / after

Before Chrome 149 — handler (SW)

self.addEventListener('paymentrequest', ev => {
  ev.respondWith(
    // Only option: reject with an Error
    // — no structured data
    Promise.reject(
      new Error('card expired')
    )
  );
});
// Merchant page
try {
  await request.show();
} catch (err) {
  // err is a generic AbortError
  // err.detail is undefined
  showGenericError(); // no info
}

Chrome 149+ — handler (SW)

self.addEventListener('paymentrequest', ev => {
  ev.respondWith(
    // Reject with structured error object
    Promise.reject({
      error: 'card_expired',
      message: 'Card expired 2024-01'
    })
  );
});
// Merchant page
try {
  await request.show();
} catch (err) {
  // err.detail is the reject object
  if (err.detail?.error === 'card_expired') {
    showCardExpiredFlow(err.detail.message);
  }
}

error object reference

Property Type Description
err.detail object | undefined The object the payment handler rejected with. undefined in pre-149 Chrome or when the handler uses a plain Error.
err.detail.error string Handler-defined error code. Not standardised — choose codes meaningful to your handler/merchant pair.
err.detail.message string Human-readable error string. May be shown to users or used for logging.
err.name "AbortError" The DOMException name — unchanged from pre-149. Error type classification still uses err.name.

full handler example

// payment-handler-sw.js
self.addEventListener('paymentrequest', async event => {
  event.respondWith(handlePayment(event));
});

async function handlePayment(event) {
  const details = event.total;

  try {
    // Open payment UI and wait for user action
    const paymentWindow = await event.openWindow('/payment-ui');

    // Wait for the UI to post back the result
    const result = await waitForPaymentResult(paymentWindow);

    if (!result.success) {
      // Chrome 149+: reject with structured error
      return Promise.reject({
        error: result.errorCode,    // e.g. 'card_expired'
        message: result.errorMessage
      });
    }

    // Resolve with PaymentResponse data
    return {
      methodName: event.methodData[0].supportedMethods,
      details: { transactionId: result.transactionId }
    };
  } catch (internalErr) {
    return Promise.reject({
      error: 'internal_error',
      message: internalErr.message
    });
  }
}

see also

implementation reference

Need the exact API surface, compatibility boundaries, errors, lifecycle, and source links? Read the matching gendn reference ↗