[w3c/payment-request] Over-reliance on AbortError (Issue #1057)

marcoscaceres created an issue (w3c/payment-request#1057)

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](index.html:1049)

**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](index.html:1067)

**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](index.html:1086)

**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](index.html:3985)

**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](index.html:2589)

**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


-- 
Reply to this email directly or view it on GitHub:
https://github.com/w3c/payment-request/issues/1057
You are receiving this because you are subscribed to this thread.

Message ID: <w3c/payment-request/issues/1057@github.com>

Received on Tuesday, 3 March 2026 03:01:44 UTC