@rnw-community/react-native-payments
Version:
Accept Payments with Apple Pay and Android Pay using the Payment Request API.
74 lines (64 loc) • 5.33 kB
Markdown
# Error handling
Every throw/reject in this package maps to one of three W3C-facing error shapes, or a plain `Error` for the one
non-spec build/config failure (the native module not being linked at all — see the table below):
- **`ConstructorError`** — a native `TypeError` (`instanceof TypeError`, `name === 'TypeError'`) for
`new PaymentRequest(...)` validation failures: missing/invalid payment methods, total, display items or
shipping options. This matches the W3C algorithm, which validates the constructor's dictionaries via WebIDL and
`check and canonicalize (total) amount`, both of which throw `TypeError`.
- **`DOMException`** (`instanceof DOMException`, `error.name` is the W3C name) — for the spec-mandated runtime
states: `AbortError`, `InvalidStateError`, `NotAllowedError`, `NotSupportedError`. `SecurityError` is defined
but not currently reachable from this implementation (no permission-policy check exists in React Native).
- **`PaymentsError`** — a plain domain error for failures the W3C spec does not name: `show()` rejecting with a
non-`Error` reason from the native module bridge (an `Error` reason is propagated **as-is** instead — see
Pitfalls), every `abort()` rejection from the native module bridge regardless of the rejection reason's type,
and a native payment response payload that fails to parse (malformed or syntactically valid but incomplete
JSON from the platform SDK, including direct construction of `AndroidPaymentResponse`/`IosPaymentResponse`
with malformed tokenization data).
```ts
import { DOMException, PaymentsError } from '@rnw-community/react-native-payments';
try {
await paymentRequest.show();
} catch (error) {
if (error instanceof DOMException && error.name === 'AbortError') {
// user cancelled
} else if (error instanceof PaymentsError && !(error instanceof DOMException)) {
// this package's own catch-all — see Pitfalls below
}
}
```
| Public API failure | Spec-mandated error | Implemented as |
| --- | --- | --- |
| `new PaymentRequest()` with no/invalid payment methods | `TypeError` | `ConstructorError` (`instanceof TypeError`) |
| `new PaymentRequest()` with missing/invalid/negative total | `TypeError` | `ConstructorError` |
| `new PaymentRequest()` with invalid display items | `TypeError` | `ConstructorError` |
| `new PaymentRequest()` with invalid shipping options | `TypeError` | `ConstructorError` |
| `new PaymentRequest()` with no platform-matching payment method | `NotSupportedError` | `DOMException` (thrown at construction, see [architecture.md](../architecture.md)) |
| `canMakePayment()` when not `created` | `InvalidStateError` | `DOMException` |
| `show()` when not `created` | `InvalidStateError` | `DOMException` |
| `show()` after the user cancels the native sheet | `AbortError` | `DOMException` |
| `abort()` when not `interactive` | `InvalidStateError` | `DOMException` |
| `abort()` resolves a pending `show()` | `AbortError` | `DOMException` |
| `PaymentRequestUpdateEvent.updateWith()` called twice for one event | `InvalidStateError` | `DOMException` |
| `PaymentResponse.complete()` / `retry()` called after `complete()` | `InvalidStateError` | `DOMException` |
| `PaymentResponse.complete()` called after `retry()` | `InvalidStateError` | `DOMException` (see [retry.md](./retry.md)) |
| `PaymentResponse.retry()` called a second time on the same response | `InvalidStateError` | `DOMException` (see [retry.md](./retry.md)) |
| `PaymentResponse.retry()` on a native binary built before this method existed | `NotSupportedError` | `DOMException` |
| Native module bridge rejects `show()` with a non-`Error` reason | _(not specified)_ | `PaymentsError` |
| Native module bridge rejects `abort()` (any reason) | _(not specified)_ | `PaymentsError` |
| Native module bridge rejects `retry()` (any reason) | _(not specified)_ | `PaymentsError` |
| Native payment response payload is malformed or incomplete JSON (incl. direct `AndroidPaymentResponse`/`IosPaymentResponse` construction) | _(not specified)_ | `PaymentsError` |
| An `updateWith()` listener answers with an invalid total/items/options | _(not specified — spec treats this as no update)_ | Logged via `console.warn`, change event answered with unchanged details |
| Native module is not linked (`Payments` bridge missing) | _(not specified — build/config error)_ | `Error` |
## Pitfalls
**Neither `error.name === 'Error'` nor `instanceof PaymentsError` alone uniquely identifies `PaymentsError`.**
`DOMException extends PaymentsError` (see [api/payments-error.md](../api/payments-error.md)), so
`instanceof PaymentsError` also matches every `DOMException`. Conversely, `show()`'s non-`Error`-reason path
propagates an already-`Error` native rejection **unchanged**, and that propagated `Error` also has
`name === 'Error'` by default without being `instanceof PaymentsError` at all. Use
`error instanceof PaymentsError && !(error instanceof DOMException)` to catch only this package's own catch-all,
as distinct from a raw `Error` propagated from the native bridge.
## References
- [api/constructor-error.md](../api/constructor-error.md)
- [api/dom-exception.md](../api/dom-exception.md)
- [api/payments-error.md](../api/payments-error.md)
- [api/payments-error-enum.md](../api/payments-error-enum.md)