Skip to content

Allow payment handlers to report back errors via Payment Request API#1050

Merged
marcoscaceres merged 5 commits intogh-pagesfrom
smcgruer-add-payment-handler-error-case
Mar 3, 2026
Merged

Allow payment handlers to report back errors via Payment Request API#1050
marcoscaceres merged 5 commits intogh-pagesfrom
smcgruer-add-payment-handler-error-case

Conversation

@stephenmcgruer
Copy link
Copy Markdown
Collaborator

@stephenmcgruer stephenmcgruer commented Nov 13, 2025

See #1040

Co-authored by: @marcoscaceres

The following tasks have been completed:

  • Modified Web platform tests (link) - not possible to test, as a test cannot make an app throw an internal error. We could potentially test this via web-based payment handlers, but those tests would only work in Chromium and not WebKit (as it doesn't ship web-based payment handlers). Otherwise we could define some sort of WebDriver API to install a fake payment app, similar to how WebAuthn works?

Implementation commitment:

  • WebKit
  • Chromium
  • Gecko (link to issue) - N/A, don't ship Payment Request

Optional, impact on Payment Handler spec?
w3c/web-based-payment-handler#428


Preview | Diff

Comment thread index.html Outdated
Comment thread index.html Outdated
Comment thread index.html Outdated
Comment thread index.html Outdated
@stephenmcgruer stephenmcgruer force-pushed the smcgruer-add-payment-handler-error-case branch from ccb85c9 to aaa261e Compare January 30, 2026 17:38
Copy link
Copy Markdown
Collaborator Author

@stephenmcgruer stephenmcgruer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I've incorporated your comments (thanks!) and put together three variants:

  1. A version where we take the unspecified error from the platform handler, and use the "let |error| be an appropriate {{DOMException}}" wording -e9eec11
  2. A version where the payment handler is presumed to pass us a DOMException directly (i.e., any conversion to DOMException happens 'inside' the relevant payment handler and any spec it has) - ae1280b
  3. A version which uses a general JavaScript value like AbortSignal does, and rejects with that reason - d976730

I personally prefer (2), but I'm open to any of them.

Comment thread index.html Outdated
Comment thread index.html Outdated
Comment thread index.html Outdated
Comment thread index.html Outdated
@marcoscaceres marcoscaceres force-pushed the smcgruer-add-payment-handler-error-case branch from d976730 to 236c39b Compare March 2, 2026 11:12
@marcoscaceres
Copy link
Copy Markdown
Member

Alternative, building on @stephenmcgruer proposal:
236c39b

PayPal's SDK exposes onError and onCancel callbacks to merchants. Currently, the Payment Request API returns AbortError for both user cancellation AND payment handler errors, forcing PayPal to parse error.message strings that vary by browser version.

Proposed Solution

Use the existing WebIDL OperationError DOMException type to distinguish payment handler errors from user cancellation (AbortError).

Two OperationError Cases

Case 1: OS-Level Error

When the operating system kills the payment UI (e.g., memory pressure when user switches apps):

User opens payment sheet → Switches to another app → 
OS kills payment UI for memory → OperationError

This is not user cancellation - the user didn't intentionally close the sheet.

Case 2: Payment Handler Internal Error

When the payment handler encounters an error during processing:

User selects PayPal → PayPal server returns 500 → 
PayPal handler signals error → OperationError

This is not user cancellation - the payment failed due to a technical issue.

Backwards Compatibility

Self-healing approach:

  1. PayPal continues using message-based detection for old browsers
  2. New browsers will throw OperationError instead of AbortError
  3. PayPal can add error.name === "OperationError" check
  4. Code naturally works in both old and new browsers:
function handlePaymentError(error) {
  // New path: check error type first
  if (error.name === "OperationError") {
    this.onError(error);
    return;
  }
  
  // Legacy path: message-based detection for old browsers
  if (error.name === "AbortError") {
    const cancelMessages = [
      "Request cancelled",
      "User closed the Payment Request UI."
    ];
    if (cancelMessages.some(m => error.message?.includes(m))) {
      this.onCancel();
    } else {
      this.onError(error);
    }
  }
}

@marcoscaceres marcoscaceres marked this pull request as ready for review March 2, 2026 21:36
@stephenmcgruer stephenmcgruer removed the request for review from rsolomakhin March 2, 2026 22:12
@stephenmcgruer
Copy link
Copy Markdown
Collaborator Author

(Removing Rouslan for review as he is no longer working on Payment Request :))

@marcoscaceres
Copy link
Copy Markdown
Member

I don't think this is testable... but we should file browser bugs and I need to check if WebKit is currently bailing out anywhere with UnknownError instead of OperationError.

@marcoscaceres
Copy link
Copy Markdown
Member

We should get an Ok from the folks in #1040 before merging, in case we've missed something... but otherwise, this seems ok to me.

@stephenmcgruer
Copy link
Copy Markdown
Collaborator Author

Filed bugs for Chromium and WebKit, filed an issue for Web-Based Payment Handler API, and added the following for WPT impact:

We could potentially test this via web-based payment handlers, but those tests would only work in Chromium and not WebKit (as it doesn't ship web-based payment handlers). Otherwise we could define some sort of WebDriver API to install a fake payment app, similar to how WebAuthn works?

@stephenmcgruer
Copy link
Copy Markdown
Collaborator Author

We should get an Ok from the folks in #1040 before merging, in case we've missed something... but otherwise, this seems ok to me.

Looks like we have that ok - @ianbjacobs any concerns/thoughts from your side, or are we good to land this?

Copy link
Copy Markdown
Collaborator

@ianbjacobs ianbjacobs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I defer to the implementers here; and you seem happy so I'm happy.

@marcoscaceres marcoscaceres merged commit 21370b1 into gh-pages Mar 3, 2026
2 checks passed
brave-builds pushed a commit to brave/chromium that referenced this pull request Mar 18, 2026
This is the first CL in a series of changes to allow web-based payment
handlers to indicate internal error. Before these changes, if a
web-based payment handler rejected the promise it passed to respondWith,
it was always treated as a "user cancelled" signal. However the Payment
Request and Web-based Payment Handler specifications are changing (see
w3c/payment-request#1050 and
w3c/web-based-payment-handler#428) to allow
reporting internal error as well.

This first CL makes the necessary changes on the service worker side and
plumbs them through to the service worker payment app code. If the
promise passed to PaymentRequestEvent.respondWith is rejected with an
"OperationError" DOMException, it is now mapped to
PAYMENT_EVENT_INTERNAL_ERROR instead of PAYMENT_EVENT_REJECT. This will
later be used in the payments code to reject the show() promise
accordingly.

Bug: 473478138
Change-Id: Ia6986035865ee4f710d0a50410bcdb14a474c945
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7639049
Commit-Queue: Stephen McGruer <smcgruer@chromium.org>
Reviewed-by: Yoshisato Yanagisawa <yyanagisawa@chromium.org>
Reviewed-by: Darwin Yang <darwinyang@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1601399}
beckysiegel pushed a commit to chromium/chromium that referenced this pull request Mar 24, 2026
This is the second CL in a series of changes to allow web-based payment
handlers to indicate internal error. Before these changes, if a
web-based payment handler rejected the promise it passed to respondWith,
it was always treated as a "user cancelled" signal. However the Payment
Request and Web-based Payment Handler specifications are changing (see
w3c/payment-request#1050 and
w3c/web-based-payment-handler#428) to allow
reporting internal error as well.

This CL implements support for OperationError (behind a base::Feature,
kPaymentRequestSupportReportingAppError) for web-based payment handlers
on Desktop only. To do so, it plumbs the mojom::PaymentEventResponseType
through OnInstrumentDetailsError down to the content::PaymentRequest,
which then uses it when rejecting the show() promise. A future CL will
implement support for consuming the mojom::PaymentEventResponseType in
the Android Payment Request implementation as well.

Note that due to crbug.com/40116807, an error dialog is still shown to
the user on desktop before show() is rejected.

Bug: 473478138
Change-Id: Iac82bce818e47317e57bf443a8c185bab43079c0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7641581
Reviewed-by: Joe Mason <joenotcharles@google.com>
Reviewed-by: Darwin Yang <darwinyang@chromium.org>
Commit-Queue: Stephen McGruer <smcgruer@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1604437}
beckysiegel pushed a commit to chromium/chromium that referenced this pull request Apr 14, 2026
This is the third CL in a series of changes to allow web-based payment
handlers to indicate internal error. Before these changes, if a
web-based payment handler rejected the promise it passed to respondWith,
it was always treated as a "user cancelled" signal. However the Payment
Request and Web-based Payment Handler specifications are changing (see
w3c/payment-request#1050 and
w3c/web-based-payment-handler#428) to allow
reporting internal error as well.

This CL passes the error reason over to Chromium Android code and
handles it in the Payment Request implementation there, so that
web-based payment handlers on Chromium for Android are also able to
indicate internal app error.

As part of the change, split up the payment_app.mojom file, as the
PaymentEventResponseType enum has to be made available to webview but we
don't want to pull in the full dependency chain of payment_app.mojom.
The split technically pulls out more than is required, but it seemed a
better conceptual fit than only pulling out the specific enum.

Bug: 473478138
Change-Id: If37ef9372555514493e523b8c72fda728768c357
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7693354
Reviewed-by: Dave Tapuska <dtapuska@chromium.org>
Commit-Queue: Stephen McGruer <smcgruer@chromium.org>
Reviewed-by: Slobodan Pejic <slobodan@chromium.org>
Reviewed-by: Dominic Farolino <dom@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1614588}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants