errors.js 1.36 KB
/**
 * 结构化计费错误(§3 契约)。所有可预期的业务失败(余额不足 / 产品缺失 /
 * 规则缺失 / 重复解锁)都以 `BillingError` 抛出,绝不返回魔法值或静默吞错。
 *
 * 字段语义:
 * - `code`:稳定枚举(见 `BillingErrorCode`),调用方据此分流,不依赖 message 文案。
 * - `retryable`:是否值得重试。业务约束类(余额不足 / 产品缺失 / 已解锁)= false;
 *   预留给底层瞬时故障(DB 抖动)时由调用方包成 retryable=true。
 * - `cause`:底层原始错误,保留链路。
 */

/**
 * 失败原因枚举(稳定字符串)。
 * @readonly
 */
export const BillingErrorCode = Object.freeze({
  INSUFFICIENT_BALANCE: 'INSUFFICIENT_BALANCE',
  PRODUCT_NOT_FOUND: 'PRODUCT_NOT_FOUND',
  RULE_NOT_FOUND: 'RULE_NOT_FOUND',
  ALREADY_UNLOCKED: 'ALREADY_UNLOCKED',
})

export class BillingError extends Error {
  /**
   * @param {object} info
   * @param {string} info.code        见 `BillingErrorCode`
   * @param {boolean} [info.retryable=false]
   * @param {unknown} [info.cause]
   * @param {string} [info.message]
   */
  constructor({ code, retryable = false, cause, message }) {
    super(message || `[billing] ${code}`)
    this.name = 'BillingError'
    this.code = code
    this.retryable = Boolean(retryable)
    if (cause !== undefined) this.cause = cause
  }
}