hcengineering/platform · error · PaymentError
Payment service error: ${response.status} ${text}
Error message
Payment service error: ${response.status} ${text} What it means
fetchSafe throws `Payment service error: ${response.status} ${text}` when the response is non-OK and its body does NOT parse as valid JSON (the JSON-parse attempt threw, so the catch branch runs). It bundles the HTTP status code and the raw body text into a PaymentError so the caller can still see what went wrong. This is the unstructured-response counterpart of the structured PaymentError path.
Source
Thrown at packages/payment-client/src/client.ts:170
* @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
- Inspect the status code and raw text in the message — HTML bodies usually indicate a proxy/gateway problem rather than the payment app itself.
- Check whether the payment service is up and whether its reverse proxy/load balancer is healthy.
- Verify the baseUrl isn't pointing at the wrong host (e.g. a proxy instead of the API).
- Retry with backoff if the status is 5xx, and alert if it persists.
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 && /Payment service error: 5\d\d/.test(e.message)) {
// gateway/server outage — retry with backoff
} else throw e
} Defensive patterns
Strategy: try-catch
Validate before calling
// pre-flight: ensure the service returns JSON (not a proxy page) before real calls
const health = await fetch(baseUrl + '/health')
const ct = health.headers.get('content-type') ?? ''
if (!ct.includes('application/json')) throw new Error('Payment endpoint not serving JSON — check proxy/baseUrl') Type guard
function isGatewayStyleError(e: unknown): boolean {
return e instanceof PaymentError && /Payment service error: (502|503|504)\b/.test(e.message)
} Try / catch
try {
await paymentClient.response('/charge', init)
} catch (e) {
if (isGatewayStyleError(e)) {
// 5xx with non-JSON body: proxy/gateway outage — retry with backoff or alert ops
} else throw e
} Prevention
- Point baseUrl directly at the payment API, not a generic proxy that may return HTML pages.
- Monitor gateway/upstream health (502/504 rates) with alerting.
- Confirm TLS and host headers are correct so the WAF/LB doesn't intercept requests.
- Treat non-JSON 5xx as infrastructure issues and page ops rather than requeuing user transactions silently.
When it happens
Trigger: Any PaymentClient call (via `response` → fetchSafe) receiving 4xx/5xx with an HTML/plain-text/empty body — e.g. an nginx 502 Bad Gateway page or a bare 'Internal Server Error' string.
Common situations: Payment service behind a reverse proxy that failed to reach the upstream (502/504), service crash returning HTML error page, load balancer maintenance page, or request blocked by a WAF returning HTML.
Related errors
- ${error.error ?? 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/89b5e946590f9477.
Report an issue: GitHub.