← Chrome 149 reference

v149 · web api · shipped

Payment Handler Internal Error Reporting

Payment handlers can now distinguish between "user cancelled" and "internal payment app error" when rejecting PaymentRequestEvent.respondWith(). Merchants receive an OperationError for app failures rather than the generic AbortError used for user cancellation — enabling more accurate error recovery flows.

at a glance

Shipped inChrome 149 / Edge 149
StatusEnabled by default
FlagNone
SpecW3C Payment Request — §"Payment handler indicates an internal error"
ChromeStatus5942637229113344 — Allow payment handlers to report back internal errors

Status disagreement, both sides reported rather than resolved (fetched 2026-10-07): this page records the feature as "Shipped in Chrome 149 / Edge 149" with status "Enabled by default", while the ChromeStatus record 5942637229113344 reports a summary status of Proposed with is_released false (accurate as of 2026-05-25), though its ship stage still carries desktop, Android, and WebView milestone 149. Neither side is adjudicated here.

why it exists

The Payment Handler API lets a web-based payment app respond to PaymentRequest.show() by handling the paymentrequest event in a service worker and calling PaymentRequestEvent.respondWith(promise). Before Chrome 149, any rejection of that promise — whether caused by the user pressing "cancel" or by a crash inside the payment app — reached the merchant as an AbortError. There was no way to tell the two apart.

This matters because the correct merchant response differs: a user cancellation should keep the checkout flow open so the user can choose a different payment method, while an internal payment app error might warrant an error message, a fallback, or a retry. The new behaviour gives payment handlers a dedicated signal for internal failures.

Source: chromestatus and blink-dev Intent to Ship thread.

shape of the change

Error type contract

Rejection with AbortError (or any non-OperationError) Treated as user cancellation. PaymentRequest.show() rejects with an AbortError on the merchant side. This is the pre-existing behaviour.
Rejection with OperationError New in Chrome 149. Treated as an internal payment app error. PaymentRequest.show() rejects with an OperationError on the merchant side, allowing the merchant to distinguish it from user cancellation.

Affected APIs

PaymentRequestEvent.respondWith() On the payment handler side (service worker). Reject the promise argument with new DOMException("…", "OperationError") to signal an internal error.
PaymentRequest.show() On the merchant side. The returned promise now rejects with either AbortError (user cancel) or OperationError (internal payment app error) — previously it always rejected with AbortError.
Source: W3C Payment Request spec.

Behavior disagreement, both sides reported rather than resolved (fetched 2026-10-07): this page documents that PaymentRequest.show() rejects with OperationError for an internal payment app error and with AbortError for user cancellation, while the MDN PaymentRequest.show() reference lists AbortError, InvalidStateError, NotSupportedError, and SecurityError as its exception types, with no OperationError case. Neither side is adjudicated here.

example

Payment handler (service-worker.js) — signal internal error

self.addEventListener("paymentrequest", (event) => {
  event.respondWith(
    new Promise(async (resolve, reject) => {
      try {
        const result = await openPaymentWindow(event);
        if (result.userCancelled) {
          // User tapped "cancel" — AbortError signals cancellation.
          reject(new DOMException("User cancelled", "AbortError"));
          return;
        }
        resolve({
          methodName: event.methodData[0].supportedMethods,
          details: result.paymentDetails,
        });
      } catch (err) {
        // Internal failure (network error, crypto failure, etc.)
        // OperationError signals an internal payment app error — new in Chrome 149.
        reject(new DOMException("Payment app error: " + err.message, "OperationError"));
      }
    })
  );
});

Merchant page — handle both error types

const request = new PaymentRequest(methodData, details);

try {
  const response = await request.show();
  await processPayment(response);
  await response.complete("success");
} catch (err) {
  if (err.name === "AbortError") {
    // User explicitly cancelled — return to checkout.
    showCheckoutForm();
  } else if (err.name === "OperationError") {
    // Payment app had an internal failure — offer fallback or retry.
    showPaymentError("The payment app encountered an error. Please try again.");
    logToMonitoring("payment_app_internal_error", err.message);
  } else {
    throw err;
  }
}

browser support

Chrome149
Edge149 (Chromium)
FirefoxNo signal (as of Chrome 149)
SafariNo signal (as of Chrome 149)
Source: chromestatus, May 2026.

Browser-support disagreement, both sides reported rather than resolved (fetched 2026-10-07): this page lists Firefox and Safari as No signal, while the ChromeStatus record 5942637229113344 reports the Firefox view as N/A with the note "Firefox does not ship Payment Request or Payment Handler" and the Safari view as Positive. Neither side is adjudicated here.

see also