# 레시피: 라이선스 키

> 데스크톱 앱, CLI, 플러그인, 템플릿을 라이선스 키와 함께 파세요. 앱은 내부에 비밀 없이 키를 검사합니다.

Perly Pay는 결제가 이행되면 키를 발급하고, 기기별 활성화를 세고, 공개 엔드포인트로 "이 키가 여기서 이 상품에 유효한가?"에 답합니다. 배포하는 프로그램은 `LicenseClient`를 쓰며, 비밀 키가 필요 없습니다.

> **참고:** 어시스턴트와 함께: 이 레시피는 Perly Pay 플러그인의 `sell-license-keys` 스킬이 내 프로젝트에서 실행합니다. 실행 가능한 예제는 플러그인 저장소의 `examples/license-keys`(MIT)입니다.

## 작동 방식

1. **키를 발급하는 상품** — `issues_license: true`와 `license_activation_limit`(키당 기기 수), 1회·평생·기간제 가격 또는 구독과 함께.
2. **판매** — 사이트의 [결제 링크](https://pay.perly.io/docs/payment-links.md), 또는 구매자마다 청구서.
3. **키 보내기** — `license.created` 웹훅으로(객체가 키), 또는 `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`이 내 키로 상품과 가격을 만들고 `.env`용 ID를 출력합니다.
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`: 실행마다 활성화를 하나씩 먹습니다.
