# 让套餐限制和定价规则远离你的公开 bundle

客户端 JavaScript 中的套餐限制、配额阈值和功能开关规则，读起来就是一份商业模式的规格说明。如何让它们不可读，以及哪些内容无论如何仍然可以被观察到。

Source: https://www.afterpack.dev/zh/docs/use-cases/pricing-logic

套餐限制、配额阈值和功能开关规则通常最终会进入客户端 bundle，竞争对手或客户可以直接从文件中读出它们。

## 在纯 JavaScript 中，你的定价表读起来就是一份规格说明

当定价表、配额映射和功能开关列表以纯 JavaScript 的形式存在时，它们读起来就是你商业模式的文档。任何人都可以打开 bundle，看到你的各个层级在哪里分界、什么会触发升级推销，以及哪些开关控制着哪些功能。

编码智能体在这个 bundle 上过一遍，就能把零散的值整理成一份有条理的规格说明。这份说明可以被照抄、在支持工单里被拿来据理力争，或者在你察觉之前就被用来绕过某个限制。

## 编码后的值，重写后的结构

在任何正的 [`complexity`](https://www.afterpack.dev/zh/docs/config#complexity) 目标下，字符串字面量都会被编码，所以套餐名称、标志键和提示信息在你提供的文件中不再是可读文本。在 `light` 下数字仍然可读：价格表或配额阈值看起来就是普通数字。从 `medium` 起，大部分整数会被编码，周围的结构也会被重写。读者啃完输出，得到的是一个等价的计算，还得从中推断你的规则是什么意思。

轮换是让恢复出来的副本无法一直有用的关键。每次发布都使用新的[种子](https://www.afterpack.dev/zh/docs/config#seed)，意味着别人上一次重建出来的东西，描述的是一个你已经不再提供的构建。

## 在存放规则的模块上提高目标

在[预设](https://www.afterpack.dev/zh/docs/presets)阶梯上提高整个 bundle，或者当预设的粒度不合适时直接设置目标。两种做法都会使页面启动时间成倍增加（[加载代价](https://www.afterpack.dev/zh/docs/presets#load-cost)），因此请先实测你的页面：

```json
{
  "preset": "medium",
  "complexity": 40
}
```

若只想标记存放规则的那个模块，就使用指令。[指令](https://www.afterpack.dev/zh/docs/directives)在 [Pro](https://www.afterpack.dev/zh/docs/tiers) 构建上生效。

```ts
/* @afterpack preset=extreme */
export function resolveQuota(plan: Plan, usage: Usage): number {
  return TIER_LIMITS[plan.tier] - usage.consumed;
}
/* @afterpack end */
```

[`strings.leaks.max`](https://www.afterpack.dev/zh/docs/config#strings-leaks-max) 把“没有可读字面量残留”变成一道构建关口，而不是靠肉眼检查的东西。失败时会给出 [`DIAG_SURVIVING_READABLE_LITERAL`](https://www.afterpack.dev/zh/docs/diagnostics)，并指向漏网的那个字面量。

## 有字面量残留时让构建失败

在 CI 中用 [`--strings.leaks.max=0`](https://www.afterpack.dev/zh/docs/config#strings-leaks-max) 构建，并把非零退出视为构建失败。然后打开 [Protection Map](https://www.afterpack.dev/zh/docs/protection-map)，检查规则模块的着色是否达到了你为它设定的目标。

## 无论你发布什么，都仍然可以被观察到的内容

轮换构建会让别人抓取的规则副本在下一次发布时过时，而这正是值得拥有的部分。这里讨论的范围是那些只为在 bundle 内部被读取而存在的值；你的 UI 渲染出来的价格或你的 API 返回的限制，无论 bundle 长什么样都可以被观察到，所以请把页面显示的任何内容都视为公开的，并在服务端强制执行每一项限制。

## 下一步

- [威胁模型](https://www.afterpack.dev/zh/docs/threat-model)：静态阅读者能恢复什么、不能恢复什么。
- [预设](https://www.afterpack.dev/zh/docs/presets)：完整的阶梯，以及每一级的复杂度目标。
- [Protection Map](https://www.afterpack.dev/zh/docs/protection-map)：确认规则模块得到了你所配置的处理。
