{"record":{"id":"7dd4f581d4bba35d","repo":"hcengineering/platform","slug":"error-error-text","errorCode":null,"errorMessage":"${error.error ?? text}","messagePattern":"\\$\\{error\\.error \\?\\? text\\}","errorType":"http","errorClass":"PaymentError","httpStatus":null,"severity":"error","filePath":"packages/payment-client/src/client.ts","lineNumber":168,"sourceCode":" * @param url - URL to fetch\n * @param init - Fetch options\n * @returns Response\n * @throws NetworkError on network issues\n * @throws PaymentError on non-ok responses\n */\nasync function fetchSafe (url: string | URL, init?: RequestInit): Promise<Response> {\n  let response\n  try {\n    response = await fetch(url, init)\n  } catch (err: any) {\n    throw new NetworkError(`Network error: ${String(err)}`)\n  }\n\n  if (!response.ok) {\n    const text = await response.text()\n    try {\n      const error = JSON.parse(text)\n      throw new PaymentError(error.error ?? text)\n    } catch {\n      throw new PaymentError(`Payment service error: ${response.status} ${text}`)\n    }\n  }\n\n  return response\n}\n","sourceCodeStart":150,"sourceCodeEnd":176,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/packages/payment-client/src/client.ts#L150-L176","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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)."],"exampleFix":"// before\nconst res = await paymentClient.response('/charge', init)\n// after\ntry {\n  const res = await paymentClient.response('/charge', init)\n} catch (e) {\n  if (e instanceof PaymentError) {\n    console.error('Payment failed:', e.message) // e.g. 'card declined'\n  } else throw e\n}","handlingStrategy":"try-catch","validationCode":"// validate request body before calling to avoid predictable 400s\nfunction assertChargeRequest(req: { amount: number; currency: string }): void {\n  if (!(req.amount > 0)) throw new Error('amount must be positive')\n  if (!/^[A-Z]{3}$/.test(req.currency)) throw new Error('invalid currency code')\n}","typeGuard":"function isPaymentError(e: unknown): e is PaymentError {\n  return e instanceof PaymentError\n}","tryCatchPattern":"try {\n  await paymentClient.response('/charge', init)\n} catch (e) {\n  if (isPaymentError(e)) {\n    const reason = e.message // server-provided error, e.g. 'card declined'\n    // branch on business failure; do not blindly retry\n  } else throw e\n}","preventionTips":["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.)."],"tags":["http","payment-error","server-error","payment"],"backgroundTag":"server-error-response","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}