v149 · Web APIs · Payment Request

Allow payment handlers to report back internal errors

Chrome 149 lets payment handler service workers distinguish internal payment-app failures from user cancellation. Previously a handler rejection was flattened into a generic abort; now an OperationError rejection from PaymentRequestEvent.respondWith() reaches the merchant as an internal app error, so checkout can retry or fall back instead of treating it as user cancellation.

concepts

  1. Real Payment Handler Error Probe

    Installs a same-origin service-worker payment handler, invokes it through PaymentRequest.show(), and compares the real merchant-side errors for success, OperationError, AbortError, and generic handler failure.

  2. Handler Integration

    Full code walkthrough: how a payment handler service worker calls respondWith() with an error, and how the merchant page catches it from show(), including the before/after API shape.

  3. Error Taxonomy

    Complete reference for every payment handler outcome — success, AbortError, OperationError, and generic errors — with an interactive test harness that shows the exact code landing in the merchant catch block for each scenario.

  4. Payment Error Dashboard

    A live analytics dashboard for payment handler errors. Run real handler outcomes one at a time, watch the error breakdown chart update, and get per-error-type remediation guidance and recommended retry logic.

  5. Retry Flow Lab

    Walk through a complete merchant checkout with five failure scenarios — card expired, insufficient funds, 3DS required, network timeout, and the old generic failure path. Each structured error drives a different recovery path: card update modal, balance top-up, 3DS challenge, timed retry, or the dead-end generic message that was the only option before Chrome 149.

why it shipped

The Payment Request API allows merchant sites to invoke registered payment handlers (browser built-ins, or third-party service worker handlers). When a handler encountered an internal error — a failed 3DS challenge, an expired card, a network timeout with the payment network — the merchant previously saw the same generic abort shape used for user cancellation. Chrome 149 preserves the handler's OperationError signal so merchants can tell "the app failed" apart from "the user cancelled".

the change

// Payment handler service worker (before Chrome 149)
self.addEventListener('paymentrequest', event => {
  event.respondWith(Promise.reject(new Error('Card expired')));
  // → merchant only sees DOMException: AbortError, no detail
});

// Payment handler service worker (Chrome 149+)
self.addEventListener('paymentrequest', event => {
  event.respondWith(
    Promise.reject(new DOMException(
      'Issuer processor timed out inside the payment app.',
      'OperationError'
    ))
  );
});

// Merchant page (Chrome 149+)
try {
  const response = await request.show();
  await response.complete('success');
} catch (err) {
  if (err.name === 'OperationError') {
    showRetryOrFallback();
  } else if (err.name === 'AbortError') {
    stopCheckoutBecauseUserCancelled();
  }
}

references

implementation reference

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