/settings/purchases
填自己的 GitHub 用户名 → 后端把他作为只读(pull)collaborator 邀请进你的私有 Org repo。
他接受邀请后 git clone,从此 git pull 就能拉你后续的所有版本。
这个设计不是 “license key + 签名下载链接”。zip 包发出去那一刻就过期了,
但 GitHub 邀请会跟着你一起成长。
门槛是怎么挡的
三层架构,故意解耦。整个流程只有一条业务规则:用户必须付过 Vibestrap 的钱。 其他都是水管。一次性配置
1. 用 Organization repo(不能是个人 repo)
GitHub 的permission: 'pull' 字段在个人 repo 上会被悄悄忽略 —— 任何被加进个人 repo 的
collaborator 自动有 push 权限,与 API body 无关。必须用 Organization repo。
如果你的开发工作在个人 repo,把 Org repo 当作 release 镜像:
2. 锁住 Org
进 Org 设置 → Member privileges:- 取消勾选 “Allow members to delete or transfer repositories” ——
即使 token 有 Administration:write,也调不了
DELETE /repos - Default repository permission 设
None - 可选:禁止 fork 私有 repo(防买家 fork 出去泄露)
3. 生成 fine-grained PAT
去 https://github.com/settings/personal-access-tokens(用 Org 成员账号登录, 建议是个 bot 账号,不是你个人账号):- Resource owner:你的 Org
- Repository access:只选买家 repo
- Permissions:
- Metadata: Read-only(GitHub 强制必填)
- Administration: Read and write(
Add a collaborator这个 endpoint 要求这个权限 —— 这是 GitHub 提供的最窄选项;防误用靠 Org 级”禁删”配置)
- Expiration:1 年,日历提醒 60 天后 rotate
src/github/invite.ts 在请求时读它,rotate 不需要重启。
4. 本地验证
买家在线上的流程
- 用户注册、付款(Stripe)
- Webhook 写一行
payment:status='paid',scene=siteConfig.product.id - 用户进
/dashboard—— 页面检测到付款记录,渲染买家视图,里面嵌着邀请表单 - 用户输 GitHub 用户名,点 Send invite
- server action 查 payment、调 GitHub、返回 reason,前端 toast 显示
- 用户在 GitHub 邮箱接受邀请,然后:
Rotation runbook
每 60 天一次(GitHub 90 天提醒邮件之前):- 生成新 PAT,scope 一致
- 更 prod env(
GITHUB_INVITE_TOKEN) - 重新部署
- 拿测试账号试一次邀请
- 验证完了去 https://github.com/settings/personal-access-tokens revoke 旧 token
排错
- 买家说”not_paid”但其实付了 —— 检查
payment.scene是否跟siteConfig.product.id完全一样 (或'vibestrap-lifetime'这个旧别名)。SQL 查一下 - 买家说”invalid_username” —— 多半拼错了。或者带了
@前缀。本地验证一下 - 所有 invite 都返 “forbidden” —— PAT 过期或 revoke 了,重新生成
- 买家收到邀请但发现没 push 权限 —— 这就是设计目标:
permission: 'pull'在 Org repo 上 只给 clone,push 会被remote rejected - 为啥不做 zip 下载? —— zip 包跟不上你后续 release。买家要的是
git pull,不是 下载完第二天就过时的 tarball
从老的 license-key 流程迁移
之前如果你跑过license 表和 LICENSE_DOWNLOAD_URL:
- 两个都没了。Schema migration 删掉
license表;env var 移除;/api/license/download路由删了 sendGitHubInviteAction里查 payment 表的 gate 替换了原本的查 license 行- 老买家(如果有)—— 手动 curl 邀请,或让他们去
/settings/purchases重新申请
源码链接
- API client:
src/github/invite.ts - Server action:
src/actions/send-github-invite.ts - UI:
src/app/[locale]/(app)/settings/purchases/ - 测试:
tests/unit/github-invite.test.ts