55 lines · 1.6 KB
export type ErrorDetail = Record<string, string | number | boolean | null | Record<string, string | null>>;

export class HttpError extends Error {
  readonly status: number;
  /** Machine-readable context for clients (e.g. which meter ran out); never secrets. */
  detail: ErrorDetail | null;

  constructor(status: number, message: string, detail: ErrorDetail | null = null) {
    super(message);
    this.name = "HttpError";
    this.status = status;
    this.detail = detail;
  }
}

export const HTTP_PAYMENT_REQUIRED = 402;

export type PaymentReason = "plan_limit" | "spending_limit" | "paid_feature";

/**
 * 402 Payment Required: the action is out of the account's budget. Clients show an upsell for `meter`
 * (its price, Pro, prepaid credit) and link to the account's billing page.
 */
export function paymentRequired(
  message: string,
  detail: { meter: string; reason: PaymentReason; accountId?: string | null },
): HttpError {
  return new HttpError(HTTP_PAYMENT_REQUIRED, message, {
    code: "payment_required",
    meter: detail.meter,
    reason: detail.reason,
    accountId: detail.accountId ?? null,
  });
}

export function badRequest(message: string): HttpError {
  return new HttpError(400, message);
}

export function unauthorized(message = "Authentication required"): HttpError {