/** * 结构化计费错误(§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 } }