Skip to content

Over-reliance on AbortError #1057

@marcoscaceres

Description

@marcoscaceres

As identified in #1040, the spec has an over reliance on AbortError...
The Payment Request specification uses AbortError inconsistently across different failure scenarios, conflating:

  • User-initiated cancellation
  • Developer errors (API misuse)
  • Security/policy violations
  • System/infrastructure failures

This makes it impossible for merchants to programmatically distinguish between these fundamentally different error categories.


Error Categories (Web Platform Conventions)

Category Appropriate Exception Semantics
Developer error InvalidStateError, NotAllowedError API called incorrectly; caller's fault
User action AbortError User explicitly cancelled
Security/policy SecurityError, NotAllowedError Permission denied, policy violation
System/infra OperationError External system failure (handler crash, OS kill)

Problematic Cases in show()

1. Document Not Visible

Location: show() method, step 6

Current behavior:

If document's visibility state is not "visible", return a promise 
rejected with an "AbortError" DOMException.

Problem: This is a developer error — the page called show() while not visible (e.g., background tab). The user didn't abort anything.

Should be: NotAllowedError


2. Payment Request Already Showing

Location: show() method, step 8

Current behavior:

If the user agent's payment request is showing boolean is true, then:
  - Set request.[[state]] to "closed"
  - Return a promise rejected with an "AbortError" DOMException.

Problem: This is a developer error — calling show() when a payment UI is already visible. The user didn't abort anything.

Should be: InvalidStateError

Rationale:

  • This is classic "object in invalid state" per WebIDL
  • Similar to calling start() on an already-started operation
  • Consistent with step 7 which uses InvalidStateError for wrong [[state]]

3. UA Immediate Abort (Private Browsing)

Location: show() method, step 12

Current behavior:

Optionally:
  - Reject acceptPromise with an "AbortError" DOMException.
  - [Note: allows UA to act as if user immediately aborted]

Problem: This conflates UA policy decisions with user cancellation. In private browsing mode, the user agent is refusing, not the user.

Should be: NotAllowedError

Rationale:

  • Private browsing = UA policy to protect privacy = NotAllowedError

4. detailsPromise Rejection

Location: Update algorithm

Current behavior:

Upon rejection of detailsPromise:
  - Abort the update with request and an "AbortError" DOMException.

Problem: If the merchant's detailsPromise rejects, that's a developer error (their server failed, their code threw). Using AbortError implies the user cancelled.

Should be: Pass through the original rejection reason, or use OperationError

Rationale:

  • The merchant's own promise failed; that's not "abort".
  • Original exception provides better debugging

Problematic Cases in retry()

5. Document Becomes Inactive During Retry

Location: retry() method

Current behavior:

If document stops being fully active while the user interface is being shown:
  - Close down the user interface
  - [No explicit rejection specified, but likely AbortError]

Problem: Document deactivation during retry is an infrastructure failure, not user cancellation.

Should be: InvalidStateError


Proposed Fix Summary

Case Current Proposed Priority
Document not visible AbortError InvalidStateError High
Already showing AbortError InvalidStateError High
Private browsing abort AbortError NotAllowedError Medium
detailsPromise rejects AbortError Original error / OperationError High
Payment handler error AbortError OperationError High

Backwards Compatibility

Changing error types is technically breaking, but:

  1. Most merchants catch all errors from show() generically
  2. Those parsing error messages are already dealing with inconsistency
  3. Semantic correctness enables better error handling going forward
  4. Can be staged: update spec, then implementations, with clear release notes

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions