hcengineering/platform · error · PaymentError
${error.error ?? text}
Error message
${error.error ?? text} What it means
When the payment service returns a non-OK HTTP status and its body parses as JSON, fetchSafe throws a PaymentError carrying the server's `error` field (falling back to the raw body text). This surfaces the backend's own explanation of the failure — declined payment, invalid request, auth rejection, etc. — to the caller as a typed error.
Source
Thrown at packages/payment-client/src/client.ts:168
* @param url - URL to fetch
* @param init - Fetch options
* @returns Response
* @throws NetworkError on network issues
* @throws PaymentError on non-ok responses
*/
async function fetchSafe (url: string | URL, init?: RequestInit): Promise<Response> {
let response
try {
response = await fetch(url, init)
} catch (err: any) {
throw new NetworkError(`Network error: ${String(err)}`)
}
if (!response.ok) {
const text = await response.text()
try {
const error = JSON.parse(text)
throw new PaymentError(error.error ?? text)
} catch {
throw new PaymentError(`Payment service error: ${response.status} ${text}`)
}
}
return response
}
View on GitHub (pinned to 63e28dc964)
Solutions
- Read the PaymentError message — it contains the server's error description identifying the exact issue.
- Fix the request payload according to the server message (validation errors, missing fields).
- Refresh/replace the auth token if the error indicates authentication failure.
- Handle PaymentError distinctly in try/catch to branch on business failures (declined vs. transient).
Example fix
// before
const res = await paymentClient.response('/charge', init)
// after
try {
const res = await paymentClient.response('/charge', init)
} catch (e) {
if (e instanceof PaymentError) {
console.error('Payment failed:', e.message) // e.g. 'card declined'
} else throw e
} Defensive patterns
Strategy: try-catch
Validate before calling
// validate request body before calling to avoid predictable 400s
function assertChargeRequest(req: { amount: number; currency: string }): void {
if (!(req.amount > 0)) throw new Error('amount must be positive')
if (!/^[A-Z]{3}$/.test(req.currency)) throw new Error('invalid currency code')
} Type guard
function isPaymentError(e: unknown): e is PaymentError {
return e instanceof PaymentError
} Try / catch
try {
await paymentClient.response('/charge', init)
} catch (e) {
if (isPaymentError(e)) {
const reason = e.message // server-provided error, e.g. 'card declined'
// branch on business failure; do not blindly retry
} else throw e
} Prevention
- Read the server's error message before retrying — 4xx business errors are not transient.
- Keep auth tokens fresh to avoid recurring 401/403 PaymentErrors.
- Log PaymentError messages with request context (without secrets) for supportability.
- Map known server error strings to domain-specific handling (declined, insufficient funds, etc.).
When it happens
Trigger: Any PaymentClient API call (via `response` → fetchSafe) receiving a 4xx/5xx response whose body is valid JSON; PaymentError(message of `error.error ?? text`) is thrown. Note: if `error.error` is itself falsy, the raw body text is used.
Common situations: Invalid or expired token (401/403), malformed charge request (400/422), insufficient funds or declined transaction from the upstream payment provider, or rate limiting (429).
Related errors
- Payment service error: ${response.status} ${text}
- ${errorBody?.error}
- Request failed
- err.message?.length > 0 ? err.message : 'Internal Server Err
- Export failed
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/7dd4f581d4bba35d.
Report an issue: GitHub.