# 配方：许可证密钥

> 用许可证密钥出售桌面应用、CLI、插件或模板。应用校验密钥时内部不含任何机密。

付款交付后，Perly Pay 发放密钥、统计各设备上的激活次数，并通过公开端点回答“这个密钥对这个商品在这里有效吗？”。你分发的程序使用 `LicenseClient` — 它不需要密钥。

> **注意:** 配合助手：Perly Pay 插件的 `sell-license-keys` 技能会在你的项目中实现这个配方。可运行的示例是插件仓库中的 `examples/license-keys`（MIT）。

## 如何运作

1. **发放密钥的商品** — `issues_license: true` 和 `license_activation_limit`（每个密钥的设备数），搭配一次性、终身、限期价格或订阅。
2. **销售** — 网站上的[付款链接](https://pay.perly.io/docs/payment-links.md)，或为每位买家开一张账单。
3. **发送密钥** — 通过 `license.created` Webhook（其对象就是密钥），或在 `invoice.fulfilled` 之后用 `licenseKeys.list({ customer })`。收据里不含密钥。
4. **在应用中激活** — `activate({ key, product, instance_name })`，名称在该设备上保持不变；保存激活 ID。
5. **启动时校验** — `validate({ key, product, instance })`。明确的 `valid: false` 就关闭；离线时，最近一次有效响应可沿用几天。

**Node**

```js
import { LicenseClient } from "perly-pay";

const licenses = new LicenseClient(); // no secret key, ever
const result = await licenses.activate({ key, product: "prod_…", instance_name: deviceName });
if (!result.valid) showWhy(result.reason); // limit_reached, revoked, expired, …
```

**PHP**

```php
use Perly\Pay\LicenseClient;

$result = (new LicenseClient())->activate(['key' => $key, 'product' => 'prod_…', 'instance_name' => home_url()]);
if (!$result['valid']) echo $result['reason'];
```

## 运行示例

1. **安装** — 在 `examples/license-keys` 中运行 `npm install`（Node 20.12+）。
2. **密钥** — 把 `.env.example` 复制为 `.env`，在 `PERLY_PAY_SECRET_KEY` 中填入测试密钥（`sk_test_…`）。
3. **准备** — `npm run setup` 用你的密钥创建商品和价格，并打印它们的 ID 供 `.env` 使用。
4. **启动** — 按其 README 运行 `npm start`（或 `npm run dev`）。
5. **无需钱包即可付款** — `npm run buy-test -- you@example.com` 在测试模式下购买一份并打印密钥；然后 `npm run app -- activate <key>` 和 `npm run app -- pro hello`。

## 陷阱

- 应用里放了 `sk_…` 密钥。任何人都能把它取出来；你分发的程序只使用 `LicenseClient`。
- 不带 `product` 的调用会被拒绝（`parameter_missing`）。每次都要传；其他商品的密钥会得到 `wrong_product`。
- 每次启动都变化的 `instance_name`：每次启动都会占用一次激活。
